├── .editorconfig ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── deploy.yml │ └── release.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .npmignore ├── .prettierignore ├── .stylelintignore ├── .stylelintrc.js ├── .template-lintrc.js ├── .watchmanconfig ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── addon ├── components │ ├── validated-button.hbs │ ├── validated-button.js │ ├── validated-button │ │ └── button.hbs │ ├── validated-form.hbs │ ├── validated-form.js │ ├── validated-input.hbs │ ├── validated-input.js │ └── validated-input │ │ ├── error.hbs │ │ ├── error.js │ │ ├── hint.hbs │ │ ├── label.hbs │ │ ├── render.hbs │ │ ├── render.js │ │ ├── render │ │ └── wrapper.hbs │ │ └── types │ │ ├── checkbox-group.hbs │ │ ├── checkbox-group.js │ │ ├── checkbox.hbs │ │ ├── checkbox.js │ │ ├── input.hbs │ │ ├── input.js │ │ ├── radio-group.hbs │ │ ├── radio-group.js │ │ ├── select.hbs │ │ ├── select.js │ │ ├── textarea.hbs │ │ └── textarea.js ├── helpers │ └── class-list.js └── passed-or-default.js ├── app ├── components │ ├── validated-button.js │ ├── validated-button │ │ └── button.js │ ├── validated-form.js │ ├── validated-input.js │ └── validated-input │ │ ├── error.js │ │ ├── hint.js │ │ ├── label.js │ │ ├── render.js │ │ ├── render │ │ └── wrapper.js │ │ └── types │ │ ├── checkbox-group.js │ │ ├── checkbox.js │ │ ├── input.js │ │ ├── radio-group.js │ │ ├── select.js │ │ └── textarea.js └── helpers │ └── class-list.js ├── blueprints └── ember-validated-form │ └── index.js ├── config ├── addon-docs.js └── deploy.js ├── demo.gif ├── ember-cli-build.js ├── index.js ├── package.json ├── pnpm-lock.yaml ├── testem.js ├── tests ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ ├── color-component.hbs │ │ │ ├── color-component.js │ │ │ ├── custom-button.hbs │ │ │ ├── custom-error.hbs │ │ │ ├── custom-hint.hbs │ │ │ ├── custom-label.hbs │ │ │ ├── favorite-colors.hbs │ │ │ ├── favorite-colors.js │ │ │ ├── flatpickr-wrapper.hbs │ │ │ ├── permanent-custom-hint.hbs │ │ │ ├── x-custom-button.hbs │ │ │ ├── x-custom-checkbox-group.hbs │ │ │ ├── x-custom-checkbox.hbs │ │ │ ├── x-custom-date.hbs │ │ │ ├── x-custom-error.hbs │ │ │ ├── x-custom-hint.hbs │ │ │ ├── x-custom-input.hbs │ │ │ ├── x-custom-label.hbs │ │ │ ├── x-custom-radio-group.hbs │ │ │ ├── x-custom-render.hbs │ │ │ ├── x-custom-render.js │ │ │ ├── x-custom-select.hbs │ │ │ └── x-custom-textarea.hbs │ │ ├── controllers │ │ │ ├── docs │ │ │ │ └── components │ │ │ │ │ └── validated-form.js │ │ │ └── index.js │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── locales │ │ │ └── fr │ │ │ │ └── translations.js │ │ ├── models │ │ │ └── user.js │ │ ├── resolver.js │ │ ├── router.js │ │ ├── routes │ │ │ ├── docs │ │ │ │ └── components │ │ │ │ │ └── validated-form.js │ │ │ └── index.js │ │ ├── services │ │ │ └── store.js │ │ ├── snippets │ │ │ ├── config-custom-date.js │ │ │ ├── config-custom-hint.js │ │ │ ├── config-defaults.js │ │ │ ├── config-features.js │ │ │ └── config-theme.js │ │ ├── styles │ │ │ └── app.css │ │ ├── templates │ │ │ ├── docs.hbs │ │ │ ├── docs │ │ │ │ ├── components │ │ │ │ │ ├── validated-button.md │ │ │ │ │ ├── validated-form.md │ │ │ │ │ └── validated-input.md │ │ │ │ ├── configuration.md │ │ │ │ ├── customization.md │ │ │ │ ├── index.md │ │ │ │ ├── migration-v6.md │ │ │ │ ├── quickstart.md │ │ │ │ ├── troubleshooting.md │ │ │ │ └── usage.md │ │ │ ├── index.hbs │ │ │ └── not-found.hbs │ │ └── validations │ │ │ └── user.js │ ├── config │ │ ├── ember-cli-update.json │ │ ├── ember-try.js │ │ ├── environment.js │ │ ├── optional-features.json │ │ └── targets.js │ └── public │ │ └── robots.txt ├── helpers │ ├── index.js │ └── scenarios.js ├── index.html ├── integration │ ├── components │ │ ├── validated-button-test.js │ │ ├── validated-button │ │ │ └── button-test.js │ │ ├── validated-form-defaults-test.js │ │ ├── validated-form-test.js │ │ ├── validated-input-test.js │ │ ├── validated-input │ │ │ ├── error-test.js │ │ │ ├── hint-test.js │ │ │ ├── label-test.js │ │ │ ├── render-test.js │ │ │ ├── render │ │ │ │ └── wrapper-test.js │ │ │ └── types │ │ │ │ ├── checkbox-group-test.js │ │ │ │ ├── checkbox-test.js │ │ │ │ ├── input-test.js │ │ │ │ ├── radio-group-test.js │ │ │ │ ├── select-test.js │ │ │ │ └── textarea-test.js │ │ └── validated-label-test.js │ └── helpers │ │ └── class-list-test.js ├── test-helper.js └── unit │ └── .gitkeep └── tsconfig.declarations.json /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | 7 | # misc 8 | /coverage/ 9 | !.* 10 | .*/ 11 | 12 | # ember-try 13 | /.node_modules.ember-try/ 14 | 15 | # snippets 16 | /tests/dummy/app/snippets/*.js 17 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | extends: ["@adfinis/eslint-config/ember-addon"], 5 | settings: { 6 | "import/internal-regex": "^(ember-validated-form|dummy)/", 7 | }, 8 | rules: { 9 | "ember/no-runloop": "warn", 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Template for bug reports 4 | --- 5 | 6 | -- 7 | If possible, please consider creating a basic reproduction of your issue with Ember Twiddle. You can use this twiddle as a starting point: 8 | 9 | https://ember-twiddle.com/3691a8576c35ff149bfc26a564ec5437 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: "friday" 8 | time: "12:00" 9 | timezone: "Europe/Zurich" 10 | - package-ecosystem: npm 11 | directory: "/" 12 | schedule: 13 | interval: "weekly" 14 | day: "friday" 15 | time: "12:00" 16 | timezone: "Europe/Zurich" 17 | open-pull-requests-limit: 10 18 | versioning-strategy: increase 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: {} 9 | 10 | concurrency: 11 | group: ci-${{ github.head_ref || github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | test: 16 | name: "Tests" 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 10 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: wyvox/action-setup-pnpm@v3 23 | with: 24 | node-version: 18 25 | - name: Lint 26 | run: pnpm lint 27 | - name: Run Tests 28 | run: pnpm ember try:each --skip-cleanup 29 | 30 | floating: 31 | name: "Floating Dependencies" 32 | runs-on: ubuntu-latest 33 | timeout-minutes: 10 34 | 35 | steps: 36 | - uses: actions/checkout@v4 37 | - uses: wyvox/action-setup-pnpm@v3 38 | with: 39 | node-version: 18 40 | args: "--no-lockfile" 41 | - name: Run Tests 42 | run: pnpm ember try:each --skip-cleanup 43 | 44 | try-scenarios: 45 | name: ${{ matrix.try-scenario }} 46 | runs-on: ubuntu-latest 47 | needs: "test" 48 | timeout-minutes: 10 49 | 50 | strategy: 51 | fail-fast: false 52 | matrix: 53 | try-scenario: 54 | - ember-lts-4.8 55 | - ember-lts-4.4 56 | - ember-lts-4.12 57 | - ember-release 58 | - ember-beta 59 | - ember-canary 60 | - embroider-safe 61 | - embroider-optimized 62 | 63 | steps: 64 | - uses: actions/checkout@v4 65 | - uses: wyvox/action-setup-pnpm@v3 66 | with: 67 | node-version: 18 68 | - name: Run Tests 69 | run: ./node_modules/.bin/ember try:each --skip-cleanup 70 | env: 71 | EMBER_SCENARIO: ${{ matrix.try-scenario }} 72 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | release: 8 | types: [published] 9 | 10 | concurrency: 11 | group: deploy 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | deploy: 16 | name: Deploy 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: wyvox/action-setup-pnpm@v3 21 | with: 22 | node-version: 18 23 | 24 | - name: Deploy to Github Pages 25 | run: pnpm ember deploy production 26 | env: 27 | CI: true 28 | DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} 29 | ADDON_DOCS_UPDATE_LATEST: true 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | release: 7 | name: Release 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | with: 12 | ref: ${{ github.event.ref }} 13 | fetch-depth: 0 14 | token: ${{ secrets.GH_TOKEN }} 15 | - uses: wyvox/action-setup-pnpm@v3 16 | with: 17 | node-version: 20 18 | 19 | - name: Configure git 20 | run: | 21 | git config user.name "${GITHUB_ACTOR}" 22 | git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" 23 | 24 | - name: Release on NPM 25 | env: 26 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 27 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 28 | run: pnpm semantic-release 29 | -------------------------------------------------------------------------------- /.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 | .idea 17 | *.swp 18 | .DS_Store 19 | .orig 20 | 21 | # ember-try 22 | /.node_modules.ember-try/ 23 | /npm-shrinkwrap.json.ember-try 24 | /package.json.ember-try 25 | /package-lock.json.ember-try 26 | /yarn.lock.ember-try 27 | 28 | # broccoli-debug 29 | /DEBUG/ 30 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | # skip in CI 5 | [ -n "$CI" ] && exit 0 6 | 7 | pnpm commitlint --edit $1 8 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | # skip in CI 5 | [ -n "$CI" ] && exit 0 6 | 7 | # lint only staged files 8 | pnpm lint-staged 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /tmp/ 4 | 5 | # misc 6 | /.editorconfig 7 | /.ember-cli 8 | /.env* 9 | /.eslintcache 10 | /.eslintignore 11 | /.eslintrc.js 12 | /.git/ 13 | /.github/ 14 | /.gitignore 15 | /.prettierignore 16 | /.prettierrc.js 17 | /.stylelintignore 18 | /.stylelintrc.js 19 | /.template-lintrc.js 20 | /.travis.yml 21 | /.watchmanconfig 22 | /CONTRIBUTING.md 23 | /ember-cli-build.js 24 | /testem.js 25 | /tests/ 26 | /yarn-error.log 27 | /yarn.lock 28 | .gitkeep 29 | 30 | # ember-try 31 | /.node_modules.ember-try/ 32 | /npm-shrinkwrap.json.ember-try 33 | /package.json.ember-try 34 | /package-lock.json.ember-try 35 | /yarn.lock.ember-try 36 | /config/addon-docs.js 37 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | 7 | # misc 8 | /coverage/ 9 | !.* 10 | .*/ 11 | 12 | # ember-try 13 | /.node_modules.ember-try/ 14 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | # unconventional files 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | 7 | # addons 8 | /.node_modules.ember-try/ 9 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | extends: ["stylelint-config-standard", "stylelint-prettier/recommended"], 5 | }; 6 | -------------------------------------------------------------------------------- /.template-lintrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | plugins: ["ember-template-lint-plugin-prettier"], 5 | extends: ["recommended", "ember-template-lint-plugin-prettier:recommended"], 6 | overrides: [ 7 | { 8 | files: ["tests/**/*"], 9 | rules: { "require-input-label": false, "no-inline-styles": false }, 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["dist"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [7.0.1](https://github.com/adfinis/ember-validated-form/compare/v7.0.0...v7.0.1) (2024-03-18) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **deps:** update dependencies ([c2d187b](https://github.com/adfinis/ember-validated-form/commit/c2d187bbbf8652f0bc30637a9b5b5f5c46e44296)) 7 | 8 | # [7.0.0](https://github.com/adfinis/ember-validated-form/compare/v6.2.0...v7.0.0) (2023-11-21) 9 | 10 | 11 | ### chore 12 | 13 | * **deps:** update ember and dependencies ([fc26b0b](https://github.com/adfinis/ember-validated-form/commit/fc26b0be302c24dafccad37a53dc7dd2bcc59bc9)) 14 | 15 | 16 | ### Features 17 | 18 | * allow html attributes to be passed to wrapping form element ([b29f4d8](https://github.com/adfinis/ember-validated-form/commit/b29f4d88133aa58a9613aa9bf9f1f6a2761a7974)) 19 | 20 | 21 | ### BREAKING CHANGES 22 | 23 | * **deps:** Drop support for Ember v3. 24 | 25 | # [6.2.0](https://github.com/adfinis/ember-validated-form/compare/v6.1.2...v6.2.0) (2023-02-23) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * scroll error into view with nested key names ([#904](https://github.com/adfinis/ember-validated-form/issues/904)) ([5c192a5](https://github.com/adfinis/ember-validated-form/commit/5c192a5b00abf05d80f36d1fad8b67a79a36e43c)) 31 | 32 | 33 | ### Features 34 | 35 | * **a11y:** add aria invalid and describedby attributes on validation ([6e16a51](https://github.com/adfinis/ember-validated-form/commit/6e16a5122f134ca991205c03c0dd8628288e4b08)), closes [#48](https://github.com/adfinis/ember-validated-form/issues/48) 36 | 37 | ## [6.1.2](https://github.com/adfinis/ember-validated-form/compare/v6.1.1...v6.1.2) (2022-09-20) 38 | 39 | 40 | ### Bug Fixes 41 | 42 | * **label:** only show requiredness indicator if a label is given ([72c212d](https://github.com/adfinis/ember-validated-form/commit/72c212dc036c643dca43e1c22a7ec43384f70ab5)) 43 | 44 | ## [6.1.1](https://github.com/adfinis/ember-validated-form/compare/v6.1.0...v6.1.1) (2022-09-20) 45 | 46 | 47 | ### Bug Fixes 48 | 49 | * **bootstrap:** make sure error feedback is always visible ([1722144](https://github.com/adfinis/ember-validated-form/commit/17221448567a4fb6dc53c39c3f42eb8eb66bdc3d)) 50 | * **checkbox-group:** fix multiple selection in checkbox group ([6bb902b](https://github.com/adfinis/ember-validated-form/commit/6bb902b0577c68671f09f9cda65b5f16ac1b8fcb)) 51 | 52 | # [6.1.0](https://github.com/adfinis/ember-validated-form/compare/v6.0.2...v6.1.0) (2022-09-16) 53 | 54 | 55 | ### Features 56 | 57 | * **input:** allow custom date component to be configured ([#861](https://github.com/adfinis/ember-validated-form/issues/861)) ([33bbafa](https://github.com/adfinis/ember-validated-form/commit/33bbafa4ac6a24d332ab4b1548abc4f2daef541f)) 58 | 59 | ## [6.0.2](https://github.com/adfinis/ember-validated-form/compare/v6.0.1...v6.0.2) (2022-09-09) 60 | 61 | 62 | ### Bug Fixes 63 | 64 | * **deps:** remove unnecessary dependency to tracked-toolbox ([605c001](https://github.com/adfinis/ember-validated-form/commit/605c0018c44dcfd99685c35b3ba656cfab420a35)) 65 | 66 | ## [6.0.1](https://github.com/adfinis/ember-validated-form/compare/v6.0.0...v6.0.1) (2022-09-08) 67 | 68 | 69 | ### Bug Fixes 70 | 71 | * **deps:** add missing dependency tracked-toolbox ([5b0f1a7](https://github.com/adfinis/ember-validated-form/commit/5b0f1a74dd33b20bcdb6bc09d55b7dc1426f5642)) 72 | 73 | # [6.0.0](https://github.com/adfinis/ember-validated-form/compare/v5.3.1...v6.0.0) (2022-09-08) 74 | 75 | 76 | ### chore 77 | 78 | * **deps:** update ember and drop node 12 support ([3f3fb67](https://github.com/adfinis/ember-validated-form/commit/3f3fb67e269b8e8e30f5939ccbbc58a01a2a6aa0)) 79 | * **select:** remove deprecated option includeBlank on selects ([3ef60b2](https://github.com/adfinis/ember-validated-form/commit/3ef60b21c1bc10259ab3865aacb5b3e2fdeda6bc)) 80 | 81 | 82 | ### Features 83 | 84 | * support embroider builds ([b0b0bde](https://github.com/adfinis/ember-validated-form/commit/b0b0bdea32a035fa499a9b0bae7ed245b4dd66f7)) 85 | 86 | 87 | ### BREAKING CHANGES 88 | 89 | * **select:** Remove deprecated option `includeBlank` on select 90 | inputs. 91 | * This changes the global configuration for features, 92 | themes and default components completely. For instructions on how to 93 | migrate, check the migration to v6 guide. 94 | * **deps:** Drop support for Node v12 and Ember LTS 3.24 95 | 96 | ## [5.3.1](https://github.com/adfinis/ember-validated-form/compare/v5.3.0...v5.3.1) (2022-09-02) 97 | 98 | ### Bug Fixes 99 | 100 | - **select:** fix promptIsSelectable in combination with targetPath ([#853](https://github.com/adfinis/ember-validated-form/issues/853)) ([526d81f](https://github.com/adfinis/ember-validated-form/commit/526d81f25eb47f938fa2fa031d5f5e710a0386bf)) 101 | 102 | # [5.3.0](https://github.com/adfinis/ember-validated-form/compare/v5.2.2...v5.3.0) (2022-02-22) 103 | 104 | ### Bug Fixes 105 | 106 | - addon docs root url ([7d8151f](https://github.com/adfinis/ember-validated-form/commit/7d8151fa9d854cdf9595f27347fa57652e58ef10)) 107 | - **blueprint:** add ember-truth-helpers to default blueprint ([a401589](https://github.com/adfinis/ember-validated-form/commit/a401589dab6d013f5d659865927919346655295d)) 108 | - **deps:** move required deps to dependencies instead of devDependencies ([46629ce](https://github.com/adfinis/ember-validated-form/commit/46629ceb48210bbc58898c299a9388ce7fa816ec)) 109 | - new link to docs ([c1073e5](https://github.com/adfinis/ember-validated-form/commit/c1073e55e3a916ed393e33bcf4a045124c320967)) 110 | - **select:** pass prompt argument down to input component ([64a1377](https://github.com/adfinis/ember-validated-form/commit/64a1377d64618fa880b332f8a693e1c7f1a321f7)) 111 | 112 | ### Features 113 | 114 | - pass attributes to input components ([dc0417d](https://github.com/adfinis/ember-validated-form/commit/dc0417d125fe4c20cb59b76531ae4f7391258259)) 115 | 116 | ## [5.2.2](https://github.com/adfinis/ember-validated-form/compare/v5.2.1...v5.2.2) (2022-02-10) 117 | 118 | ### Bug Fixes 119 | 120 | - select support for plain options ([#747](https://github.com/adfinis/ember-validated-form/issues/747)) ([a58e26d](https://github.com/adfinis/ember-validated-form/commit/a58e26ddcd46ec5328c3bb5351bafc7781eacdbd)) 121 | 122 | ## [5.2.1](https://github.com/adfinis/ember-validated-form/compare/v5.2.0...v5.2.1) (2022-02-09) 123 | 124 | ### Bug Fixes 125 | 126 | - respect scrollErrorIntoView for validated buttons as well ([#743](https://github.com/adfinis/ember-validated-form/issues/743)) ([fd6be2a](https://github.com/adfinis/ember-validated-form/commit/fd6be2a49c5f947bfcf5eb3f7ab61a23ac00064a)) 127 | 128 | # [5.2.0](https://github.com/adfinis/ember-validated-form/compare/v5.1.1...v5.2.0) (2022-02-03) 129 | 130 | ### Features 131 | 132 | - scroll first invalid element into view ([#733](https://github.com/adfinis/ember-validated-form/issues/733)) ([ae7c8b2](https://github.com/adfinis/ember-validated-form/commit/ae7c8b2b160307646adf90cd09a091effa549238)) 133 | - **validated-button:** add `triggerValidations` flag ([#721](https://github.com/adfinis/ember-validated-form/issues/721)) ([765f5f4](https://github.com/adfinis/ember-validated-form/commit/765f5f40c9d2e5ccfca7129fb701ff3bb0ed661e)) 134 | 135 | # [5.0.0](https://github.com/adfinis/ember-validated-form/compare/v4.1.0...v5.0.0) (2021-10-08) 136 | 137 | ### chore 138 | 139 | - **deps:** update ember and other dependencies ([41e099c](https://github.com/adfinis/ember-validated-form/commit/41e099c4da82135c562493e5b2a4f9420dca73c6)) 140 | - **ember:** remove support for ember 3.20 ([0cfebfc](https://github.com/adfinis/ember-validated-form/commit/0cfebfcc5792a1df52093a972878af1617ec8100)) 141 | 142 | ### Features 143 | 144 | - refactor all components to glimmer and use native classes ([cee7373](https://github.com/adfinis/ember-validated-form/commit/cee7373a3c0783a02fe00b5e510c41ba604403c2)) 145 | 146 | ### BREAKING CHANGES 147 | 148 | - **ember:** Remove support for ember LTS 3.20 since that version 149 | has a bug with autotracking. 150 | - **deps:** Require `ember-auto-import` v2+ 151 | - While the public API won't change, there is a huge 152 | chance that this will break implementations if someone's extending the 153 | components of this addon. Components that do need to be refactored to 154 | glimmer. 155 | 156 | # [4.1.0](https://github.com/adfinis/ember-validated-form/compare/v4.0.1...v4.1.0) (2021-09-30) 157 | 158 | ### Bug Fixes 159 | 160 | - **deps:** [security] bump handlebars from 4.7.6 to 4.7.7 ([#588](https://github.com/adfinis/ember-validated-form/issues/588)) ([d167207](https://github.com/adfinis/ember-validated-form/commit/d167207ee059bd9b968a08fb61f18f43dadab0ab)) 161 | - **deps:** [security] bump striptags from 3.1.1 to 3.2.0 ([#637](https://github.com/adfinis/ember-validated-form/issues/637)) ([3632f52](https://github.com/adfinis/ember-validated-form/commit/3632f52e7fa1fc6f0e17dd3365a74a80ceb92833)) 162 | - **deps:** [security] bump trim-newlines from 3.0.0 to 3.0.1 ([#634](https://github.com/adfinis/ember-validated-form/issues/634)) ([10e3974](https://github.com/adfinis/ember-validated-form/commit/10e397452b30a3af11a299c5bcdafd703d9b0c18)) 163 | - **deps:** [security] bump ws from 6.2.1 to 6.2.2 ([#624](https://github.com/adfinis/ember-validated-form/issues/624)) ([910ec64](https://github.com/adfinis/ember-validated-form/commit/910ec64b2f84562fd77a8be14094fd2f326d60b6)) 164 | - call on-update hook correctly ([#641](https://github.com/adfinis/ember-validated-form/issues/641)) ([b8688b6](https://github.com/adfinis/ember-validated-form/commit/b8688b6d9dedbd9096d34c3b236bf59efb045556)) 165 | 166 | ### Features 167 | 168 | - checkbox groups ([#640](https://github.com/adfinis/ember-validated-form/issues/640)) ([9099ce8](https://github.com/adfinis/ember-validated-form/commit/9099ce81bbedc53c961653dca59c555d96ee9128)) 169 | 170 | ## [4.0.1](https://github.com/adfinis/ember-validated-form/compare/v4.0.0...v4.0.1) (2021-05-21) 171 | 172 | ### Bug Fixes 173 | 174 | - **validated-input:** use changeset.set if available to preserve state tracking on nested objects ([#609](https://github.com/adfinis/ember-validated-form/issues/609)) ([d3b92ee](https://github.com/adfinis/ember-validated-form/commit/d3b92ee5dfb7e0a6f4fbdb1899d9be34b67d1722)) 175 | 176 | # [4.0.0](https://github.com/adfinis/ember-validated-form/compare/v3.0.3...v4.0.0) (2021-05-19) 177 | 178 | ### Bug Fixes 179 | 180 | - **validated-input:** rewrite to glimmer and support nested changesets ([#581](https://github.com/adfinis/ember-validated-form/issues/581)) ([2f3e7c5](https://github.com/adfinis/ember-validated-form/commit/2f3e7c5c9e13ad39ecba9358305cfcc4bac8f6b8)) 181 | 182 | ### BREAKING CHANGES 183 | 184 | - **validated-input:** This drops support for Ember LTS 3.16 and `ember-changeset` < 3.0.0 and `ember-changeset-validations` < 3.0.0 185 | 186 | - refactor(validated-input): refactor dynamic component call to angle-brackets 187 | 188 | - chore(\*): drop node v10 support 189 | - **validated-input:** drop node v10 support since v10 has reached EOL 190 | 191 | - fix(themed-component): convert array to string befor using in key path 192 | 193 | ## [3.0.3](https://github.com/adfinis/ember-validated-form/compare/v3.0.2...v3.0.3) (2021-04-15) 194 | 195 | ### Bug Fixes 196 | 197 | - **deps:** [security] bump elliptic from 6.5.3 to 6.5.4 ([#538](https://github.com/adfinis/ember-validated-form/issues/538)) ([b36030c](https://github.com/adfinis/ember-validated-form/commit/b36030cd289485154fbaaefe914535cad7639043)) 198 | - **deps:** [security] bump ini from 1.3.5 to 1.3.8 ([#450](https://github.com/adfinis/ember-validated-form/issues/450)) ([97870d1](https://github.com/adfinis/ember-validated-form/commit/97870d152b12a273328fae257fb7399a067d7f9c)) 199 | - **deps:** [security] bump socket.io from 2.3.0 to 2.4.1 ([#474](https://github.com/adfinis/ember-validated-form/issues/474)) ([fe46f19](https://github.com/adfinis/ember-validated-form/commit/fe46f197439ff85170c92b6236f73c34f459d01f)) 200 | - **deps:** [security] bump y18n from 3.2.1 to 3.2.2 ([#560](https://github.com/adfinis/ember-validated-form/issues/560)) ([727144e](https://github.com/adfinis/ember-validated-form/commit/727144edc1920d323aee882240de695ca5f0e228)) 201 | - remove unused modules which lead to invalid imports ([#464](https://github.com/adfinis/ember-validated-form/issues/464)) ([d0405bf](https://github.com/adfinis/ember-validated-form/commit/d0405bf8733119394c953f73155349f65e2e5a88)) 202 | - **deps:** bump ember-auto-import from 1.10.1 to 1.11.2 ([#557](https://github.com/adfinis/ember-validated-form/issues/557)) ([f01f882](https://github.com/adfinis/ember-validated-form/commit/f01f8826fe6cddfa1d5776960fb773973a25545d)) 203 | - **deps:** bump ember-auto-import from 1.7.0 to 1.10.1 ([#456](https://github.com/adfinis/ember-validated-form/issues/456)) ([d4940d7](https://github.com/adfinis/ember-validated-form/commit/d4940d7cf6a367184dccd0e6677f3bdae6afdee8)) 204 | - **deps:** bump ember-cli-babel from 7.23.0 to 7.23.1 ([#473](https://github.com/adfinis/ember-validated-form/issues/473)) ([df7cec0](https://github.com/adfinis/ember-validated-form/commit/df7cec06c35b0665df6e177f495360a14572cb73)) 205 | - **deps:** bump ember-cli-babel from 7.23.1 to 7.26.3 ([#558](https://github.com/adfinis/ember-validated-form/issues/558)) ([6b0ee99](https://github.com/adfinis/ember-validated-form/commit/6b0ee99faf936d329378959c63303a18fd855912)) 206 | - **deps:** bump ember-cli-htmlbars from 5.6.4 to 5.7.1 ([#549](https://github.com/adfinis/ember-validated-form/issues/549)) ([e00e417](https://github.com/adfinis/ember-validated-form/commit/e00e41791039ce1b410f442900b8f079a42cae4d)) 207 | - **deps:** bump ember-one-way-select from 4.0.0 to 4.0.1 ([#565](https://github.com/adfinis/ember-validated-form/issues/565)) ([0564346](https://github.com/adfinis/ember-validated-form/commit/05643467b6edffc6e08eb067f77acdd79161ae5b)) 208 | - **deps:** bump uuid from 8.3.1 to 8.3.2 ([#439](https://github.com/adfinis/ember-validated-form/issues/439)) ([d72f013](https://github.com/adfinis/ember-validated-form/commit/d72f0130f5b34cda2bda3b82da5204403d026541)) 209 | 210 | ## [3.0.2](https://github.com/adfinis/ember-validated-form/compare/v3.0.1...v3.0.2) (2020-11-20) 211 | 212 | ### Bug Fixes 213 | 214 | - **intl:** remove unused translation that caused warnings in apps ([f20f233](https://github.com/adfinis/ember-validated-form/commit/f20f2332cbf8cf70711f292864e7a9dd2df8b995)) 215 | 216 | ## [3.0.1](https://github.com/adfinis/ember-validated-form/compare/v3.0.0...v3.0.1) (2020-11-18) 217 | 218 | ### Bug Fixes 219 | 220 | - **deps:** update ember-changeset and validations to v3+ ([a9d83e2](https://github.com/adfinis/ember-validated-form/commit/a9d83e208ad2599b0bd3847bc170179294d479ad)) 221 | 222 | # [3.0.0](https://github.com/adfinis/ember-validated-form/compare/v2.0.0...v3.0.0) (2020-11-06) 223 | 224 | - chore!: update dependencies (#392) ([2664399](https://github.com/adfinis/ember-validated-form/commit/266439966050dee4ec6033f24828c84f5e0543b8)), closes [#392](https://github.com/adfinis/ember-validated-form/issues/392) 225 | 226 | ### Bug Fixes 227 | 228 | - **deps:** bump ember-auto-import from 1.6.0 to 1.7.0 ([#394](https://github.com/adfinis/ember-validated-form/issues/394)) ([9d464e4](https://github.com/adfinis/ember-validated-form/commit/9d464e4b5e83f19f3bd46897ef56a3caaaeb7153)) 229 | - **select:** pass promptIsSelectable to select ([e739b4f](https://github.com/adfinis/ember-validated-form/commit/e739b4fea27165e97ae0130e5d330990bdf501ee)) 230 | 231 | ### BREAKING CHANGES 232 | 233 | - This drops support for Ember LTS 3.8 and 3.12 234 | 235 | # Change Log 236 | 237 | All notable changes to this project will be documented in this file. 238 | 239 | The format is based on [Keep a Changelog](https://keepachangelog.com/) 240 | and this project adheres to [Semantic Versioning](https://semver.org/). 241 | 242 | ## [1.4.2] 243 | 244 | ### Changed 245 | 246 | - Bump version because of a wrong npm publish 247 | 248 | ## [1.4.1] 249 | 250 | ### Changed 251 | 252 | - Fix changing of a changeset property from outside of the form (#118) 253 | 254 | ## [1.4.0] 255 | 256 | ### Changed 257 | 258 | - Update ember to version 3.1 259 | - Fix mixed content in docs (#107 / #108, credits to @sliverc) 260 | - Fix wrong bootstrap class in documentation (#114, credits to @kimroen) 261 | 262 | ### Added 263 | 264 | - `on-invalid-submit` action gets called on submitting an invalid form (#111, credits to @omairvaiyani) 265 | - `inputId` is yielded from the `validated-input` component (#115, credits to @kimroen) 266 | 267 | ## [1.3.1] 268 | 269 | ### Changed 270 | 271 | - Addon docs with [`ember-cli-addon-docs`](https://github.com/ember-learn/ember-cli-addon-docs) 272 | 273 | ## [1.3.0] 274 | 275 | ### Changed 276 | 277 | - Bootstrap 4 support (#100, credits to @anehx) 278 | - Add valid and invalid class to input component (#100, credits to @anehx) 279 | - Only set valid class if value is dirty (#100, credits to @anehx) 280 | 281 | ## [1.2.0] 282 | 283 | ### Changed 284 | 285 | - Add support for custom label components (#95, credits to @przywartya) 286 | - Allow setting autocomplete attribute on validated-form and validated-input (#97, credits to @makepanic) 287 | 288 | ## [1.1.0] 289 | 290 | ### Changed 291 | 292 | - Update ember-cli to 3.0.0, update dependencies 293 | 294 | ## [1.0.1] 295 | 296 | ### Changed 297 | 298 | - fix: use radio value to expose option key (#91, credits to @makepanic) 299 | 300 | ## [1.0.0] 301 | 302 | ### Changed 303 | 304 | - Install `ember-changeset` and `ember-changeset-validations` from the blueprint (#86, credits to @bendemboski) 305 | - Update dependencies (#87, credits to @bendemboski) 306 | - Remove deprecated task support (#88, credits to @bendemboski) 307 | 308 | The 1.0.0 release removes ember-changeset and ember-changeset-validations as dependencies because this addon is built to make them very easy to use, but doesn't require them. So if you are using either or both of them in your application code but do not already have them in your package.json, you should run 309 | 310 | ```bash 311 | ember install ember-changeset 312 | ember install ember-changeset-validations 313 | ``` 314 | 315 | This release also removes the previously deprecated API for passing ember-concourrency tasks to `on-submit`. Please see section [0.6.2] for migration instructions. 316 | 317 | ## [0.7.1] 318 | 319 | ### Changed 320 | 321 | - Fix for removal from DOM during submit (#85, credits to @bendemboski) 322 | 323 | ## [0.7.0] 324 | 325 | ### Changed 326 | 327 | - Wrap radio, checkbox in span to allow custom styles (#82, credits to @makepanic) 328 | 329 | ## [0.6.4] 330 | 331 | ### Changed 332 | 333 | - Update ember truth helpers (#79, credits to @bendemboski) 334 | - Update ember-cli to 2.18.0 (#80) 335 | 336 | ## [0.6.3] 337 | 338 | ### Changed 339 | 340 | - Add disabled flag on checkbox instance (#77, credits to @toumbask) 341 | 342 | ## [0.6.2] 343 | 344 | ### Changed 345 | 346 | - Yield loading state, change `on-submit` to promise semantics (#75, credits to @bendemboski) 347 | 348 | This release deprecates passing an ember-concurrency task directly to `on-submit`: 349 | 350 | ```Handlebars 351 | // deprecated: 352 | {{#validated-form on-submit = myTask (...)}} 353 | ``` 354 | 355 | Instead, `on-submit` accepts a promise - which is returned by wrapping the task in `perform`: 356 | 357 | ```Handlebars 358 | {{#validated-form on-submit = (perform myTask) (...)}} 359 | ``` 360 | 361 | ## [0.6.1] 362 | 363 | ### Added 364 | 365 | - Add more input attributes (#71, credits to @bendemboski) 366 | - Add validateBeforeSubmit option (#70, credits to @bendemboski) 367 | 368 | ## [0.6.0] 369 | 370 | ### Changed 371 | 372 | - Use yarn instead of npm (#62) 373 | - Update dependencies (#62) 374 | - Remove automatic disabling of submit button while task is running (#63) 375 | 376 | To restore the old behaviour, all you have to do is pass the `isRunning` state as `disabled` property to the submit button: 377 | 378 | ```Handlebars 379 | {{#validated-form 380 | ... 381 | on-submit = myTask 382 | as |f|}} 383 | {{f.submit label="Test" disabled=myTask.isRunning}} 384 | {{/validated-form}} 385 | ``` 386 | 387 | ### Added 388 | 389 | - Add loading class to button if task is running (#63) 390 | - Support block style usage of submit button (#61) 391 | 392 | ### Removed 393 | 394 | - Useless class name binding on button (#65) 395 | - Dependency to ember-data (#62) 396 | - Various unused dependencies (#62) 397 | 398 | ## [0.5.4] 399 | 400 | ### Changed 401 | 402 | - Input, Textarea: Use native input tag instead of one-way-input (#60) 403 | - Dummy app: load bootstrap from CDN (#59) 404 | 405 | ## [0.5.3] 406 | 407 | ### Changed 408 | 409 | - Fix bug that caused ID collisions when multiple forms on the same page use inputs with 410 | the same name (#55, credits to @anehx) 411 | 412 | ## [0.5.2] 413 | 414 | ### Changed 415 | 416 | - Update ember-cli to v2.5.1 (#53) 417 | 418 | ## [0.5.1] 419 | 420 | ### Changed 421 | 422 | - Add proper `id` attributes to select and simple form components (#51) 423 | 424 | ## [0.5.0] 425 | 426 | ### Changed 427 | 428 | - Separate classes for field hints and validation messages (#42, credits to @jacob-financit). If you've 429 | been using the (previously undocumented) option `help` on input fields, you'll have to rename them to `hint`. 430 | 431 | ## [0.4.2] 432 | 433 | ### Added 434 | 435 | - Better documentation for custom component integration (#46) 436 | 437 | ### Changed 438 | 439 | - Fix bug that causes form to break for select fields without option `promptIsSelectable` (#45) 440 | 441 | ## [0.4.1] 442 | 443 | ### Added 444 | 445 | - Support selectable prompts from the select dropdown (#40, credits to @steverhoades) 446 | 447 | ### Changed 448 | 449 | - Update dependencies (#33, #34, credits to @okachynskyy) 450 | - Move addon from pods to default structure (#32, credits to @okachynskyy) 451 | 452 | ## [0.4.0] 453 | 454 | ### Added 455 | 456 | - Support block form usage of radioGroups (#28, credits to @jacob-financeit) 457 | 458 | ### Changed 459 | 460 | - remove hardcoded `radio` class for radio button groups. If you're using bootstrap, you 461 | might have to add `radio: 'radio'` to the CSS config. 462 | 463 | ## [0.3.1] 464 | 465 | ### Added 466 | 467 | - More CSS configuration options and docs on how to integrate semantic UI (#26) 468 | 469 | ## [0.3.0] 470 | 471 | ### Changed 472 | 473 | - Removed automatic inference if field is required (which appended "\*" to labels) because it 474 | was incorrect. (#27, credits to @andreabettich) Now, fields have to be marked as required explicitly: 475 | 476 | ```Handlebars 477 | {{f.input label="First name" name="firstName" required=true}} 478 | ``` 479 | 480 | ## [0.2.2] 481 | 482 | - Upgrade to ember 2.12.0 (#24, credits to @sproj) 483 | 484 | ## [0.2.1] 485 | 486 | ### Added 487 | 488 | - Override initial value of input field using `value` attribute (#22, credits to @kaldras) 489 | - Document `on-update` property for custom update functions of input elements, 490 | and add `changeset` argument to its signature 491 | 492 | ## [0.2.0] 493 | 494 | ### Changed 495 | 496 | - yield `submit` button instead of automatically rendering it, removed `cancel` button. Migration is simple: 497 | 498 | Before: 499 | 500 | ```Handlebars 501 | {{#validated-form 502 | model = (changeset model UserValidations) 503 | on-submit = (action "submit") 504 | on-cancel = (action "cancel") 505 | submit-label = 'Save' as |f|}} 506 | {{!-- form content --}} 507 | {{/validated-form}} 508 | ``` 509 | 510 | After: 511 | 512 | ```Handlebars 513 | {{#validated-form 514 | model = (changeset model UserValidations) 515 | on-submit = (action "submit") 516 | as |f|}} 517 | {{!-- form content --}} 518 | {{f.submit label='Save'}} 519 | 520 | {{/validated-form}} 521 | ``` 522 | 523 | ## [0.1.11] - 2017-03-23 524 | 525 | ### Added 526 | 527 | - Checkbox support (#5, credits to @psteininger) 528 | 529 | ## [0.1.10] - 2017-03-23 530 | 531 | ### Added 532 | 533 | - Easy integration of custom components (#17, credits to @feanor07) 534 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | ## Installation 4 | 5 | - `git clone ` 6 | - `cd ember-validated-form` 7 | - `pnpm install` 8 | 9 | ## Linting 10 | 11 | - `pnpm lint` 12 | - `pnpm lint:fix` 13 | 14 | ## Running tests 15 | 16 | Since `ember-validated-form` uses build time configuration, there are various 17 | test scenarios which can't be tested in the same run as the build changes each 18 | time. The following scenarios exist and contain tests: 19 | 20 | - `THEME_UIKIT`, tests UIkit specific code 21 | - `THEME_BOOTSTRAP`, tests Bootstrap specific code 22 | - `THEME_DEFAULT`, tests theme unspecific code 23 | - `CUSTOM_COMPONENTS`, tests custom default components 24 | 25 | In order to be sure that the written code works, the developer needs to make 26 | sure that each scenario succeeds. For that, `ember-try` was configured 27 | accordingly to test each of those scenarios: 28 | 29 | - `ember try:each` – Runs the test suite on the current Ember version 30 | - `TEST_SCENARIO=[...] ember test --server` – Runs the test suite with the given scenario in "watch mode" 31 | - `EMBER_SCENARIO=[...] ember try:each` – Runs the test suite against multiple Ember versions 32 | 33 | ## Running the dummy application 34 | 35 | - `ember serve` 36 | - Visit the dummy application at [http://localhost:4200](http://localhost:4200). 37 | 38 | For more information on using ember-cli, visit [https://cli.emberjs.com/release/](https://cli.emberjs.com/release/). 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ember-validated-form 2 | 3 | [![npm version](https://badge.fury.io/js/ember-validated-form.svg)](https://badge.fury.io/js/ember-validated-form) 4 | [![Ember Observer Score](https://emberobserver.com/badges/ember-validated-form.svg)](https://emberobserver.com/addons/ember-validated-form) 5 | [![Test](https://github.com/adfinis/ember-validated-form/workflows/Test/badge.svg)](https://github.com/adfinis/ember-validated-form/actions?query=workflow%3ATest) 6 | [![Code Style: Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 7 | 8 | Easily create forms with client side validations. 9 | 10 | ![gif](https://raw.githubusercontent.com/adfinis/ember-validated-form/main/demo.gif) 11 | 12 | Want to try it yourself? [View the docs here.](https://adfinis.github.io/ember-validated-form) 13 | 14 | # Contributing 15 | 16 | Bug reports, suggestions and pull requests are always welcome! 17 | 18 | See the [Contributing](CONTRIBUTING.md) guide for details. 19 | 20 | ### License 21 | 22 | This project is licensed under the [MIT License](LICENSE.md). 23 | -------------------------------------------------------------------------------- /addon/components/validated-button.hbs: -------------------------------------------------------------------------------- 1 | {{#if (has-block)}} 2 | {{yield}} 10 | {{else}} 11 | 19 | {{/if}} -------------------------------------------------------------------------------- /addon/components/validated-button.js: -------------------------------------------------------------------------------- 1 | import { action } from "@ember/object"; 2 | import { macroCondition, getOwnConfig } from "@embroider/macros"; 3 | import Component from "@glimmer/component"; 4 | import { tracked } from "@glimmer/tracking"; 5 | import { resolve } from "rsvp"; 6 | 7 | import passedOrDefault from "ember-validated-form/passed-or-default"; 8 | 9 | const ON_CLICK = "on-click"; 10 | const ON_INVALID_CLICK = "on-invalid-click"; 11 | export default class ValidatedButtonComponent extends Component { 12 | @tracked _loading; 13 | 14 | @passedOrDefault("button") buttonComponent; 15 | 16 | get loading() { 17 | return this.args.loading || this._loading; 18 | } 19 | 20 | @action 21 | async click(event) { 22 | // handle only clicks for custom buttons 23 | // everything else is handled by the validated form itself 24 | if (this.args.type !== "button") { 25 | return this.args.action(event); 26 | } 27 | 28 | event.preventDefault(); 29 | 30 | if (this.args.triggerValidations) { 31 | this.args.markAsDirty(); 32 | } 33 | 34 | const model = this.args.model; 35 | 36 | if (!model || !model.validate) { 37 | this.runCallback(ON_CLICK); 38 | return; 39 | } 40 | 41 | await model.validate(); 42 | 43 | if (macroCondition(getOwnConfig().scrollErrorIntoView)) { 44 | if (model.errors[0]?.key) { 45 | document 46 | .querySelector(`[name=${model.errors[0].key.replaceAll(".", "\\.")}]`) 47 | ?.scrollIntoView({ behavior: "smooth" }); 48 | } 49 | } 50 | 51 | if (model.get("isInvalid")) { 52 | this.runCallback(ON_INVALID_CLICK); 53 | } else { 54 | this.runCallback(ON_CLICK); 55 | } 56 | } 57 | 58 | runCallback(callbackProp) { 59 | const callback = this.args[callbackProp]; 60 | if (typeof callback !== "function") { 61 | return; 62 | } 63 | 64 | this._loading = true; 65 | resolve(callback(this.args.model)).finally(() => { 66 | this._loading = false; 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /addon/components/validated-button/button.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addon/components/validated-form.hbs: -------------------------------------------------------------------------------- 1 |
6 | {{yield 7 | (hash 8 | model=@model 9 | loading=this.loading 10 | input=(component 11 | "validated-input" 12 | model=@model 13 | submitted=this.submitted 14 | validateBeforeSubmit=@validateBeforeSubmit 15 | ) 16 | submit=(component 17 | "validated-button" 18 | type="submit" 19 | loading=this.loading 20 | label="Save" 21 | action=this.submit 22 | ) 23 | button=(component 24 | "validated-button" 25 | type="button" 26 | loading=this.loading 27 | label="Action" 28 | model=@model 29 | markAsDirty=this.markAsDirty 30 | ) 31 | ) 32 | }} 33 |
-------------------------------------------------------------------------------- /addon/components/validated-form.js: -------------------------------------------------------------------------------- 1 | import { action } from "@ember/object"; 2 | import { scheduleOnce } from "@ember/runloop"; 3 | import { macroCondition, getOwnConfig } from "@embroider/macros"; 4 | import Component from "@glimmer/component"; 5 | import { tracked } from "@glimmer/tracking"; 6 | import { resolve } from "rsvp"; 7 | 8 | const PROP_ON_SUBMIT = "on-submit"; 9 | const PROP_ON_INVALID_SUBMIT = "on-invalid-submit"; 10 | 11 | export default class ValidatedFormComponent extends Component { 12 | @tracked loading = false; 13 | @tracked submitted = false; 14 | @tracked validateBeforeSubmit = true; 15 | 16 | constructor(...args) { 17 | super(...args); 18 | 19 | if (this.args.model && this.args.model.validate) { 20 | scheduleOnce("actions", this, "validateModel", this.args.model); 21 | } 22 | } 23 | 24 | validateModel(model) { 25 | model.validate(); 26 | } 27 | 28 | @action 29 | markAsDirty() { 30 | this.submitted = true; 31 | } 32 | 33 | @action 34 | async submit(event) { 35 | event.preventDefault(); 36 | 37 | this.submitted = true; 38 | const model = this.args.model; 39 | 40 | if (!model || !model.validate) { 41 | this.runCallback(PROP_ON_SUBMIT); 42 | return false; 43 | } 44 | 45 | await model.validate(); 46 | 47 | if (model.get("isInvalid")) { 48 | if (macroCondition(getOwnConfig().scrollErrorIntoView)) { 49 | if (model.errors[0]?.key) { 50 | document 51 | .querySelector( 52 | `[name=${model.errors[0].key.replaceAll(".", "\\.")}]`, 53 | ) 54 | ?.scrollIntoView({ behavior: "smooth" }); 55 | } 56 | } 57 | this.runCallback(PROP_ON_INVALID_SUBMIT); 58 | } else { 59 | this.runCallback(PROP_ON_SUBMIT); 60 | } 61 | 62 | return false; 63 | } 64 | 65 | runCallback(callbackProp) { 66 | const callback = this.args[callbackProp]; 67 | if (typeof callback !== "function") { 68 | return; 69 | } 70 | 71 | this.loading = true; 72 | resolve(callback(this.args.model)).finally(() => { 73 | this.loading = false; 74 | }); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /addon/components/validated-input.hbs: -------------------------------------------------------------------------------- 1 | {{#if (has-block)}} 2 | 9 | 10 | {{yield 11 | (hash 12 | value=this._val 13 | update=this.update 14 | setDirty=this.setDirty 15 | model=@model 16 | name=@name 17 | inputId=this.inputId 18 | isValid=this.isValid 19 | isInvalid=this.isInvalid 20 | ) 21 | }} 22 | 23 | {{#if @hint}} 24 | 25 | {{/if}} 26 | 27 | {{#if (and this.showValidity this.errors)}} 28 | 29 | {{/if}} 30 | {{else}} 31 | 83 | {{/if}} -------------------------------------------------------------------------------- /addon/components/validated-input.js: -------------------------------------------------------------------------------- 1 | import { action, set, get } from "@ember/object"; 2 | import { guidFor } from "@ember/object/internals"; 3 | import { isEmpty } from "@ember/utils"; 4 | import Component from "@glimmer/component"; 5 | import { tracked } from "@glimmer/tracking"; 6 | 7 | import passedOrDefault from "ember-validated-form/passed-or-default"; 8 | 9 | /** 10 | * This component wraps form inputs. 11 | * 12 | * It can be used in a two-way-binding style like 13 | * (model will be updated) 14 | * 15 | * or in a one-way-binding style 16 | * 3 | {{yield}}{{this.errorString}} 4 | 5 | {{else}} 6 | 14 | {{yield}}{{this.errorString}} 15 | 16 | {{/if}} -------------------------------------------------------------------------------- /addon/components/validated-input/error.js: -------------------------------------------------------------------------------- 1 | import Component from "@glimmer/component"; 2 | 3 | export default class ErrorComponent extends Component { 4 | get errorString() { 5 | return this.args.errors?.join(", "); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /addon/components/validated-input/hint.hbs: -------------------------------------------------------------------------------- 1 | {{yield}}{{@hint}} -------------------------------------------------------------------------------- /addon/components/validated-input/label.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addon/components/validated-input/render.hbs: -------------------------------------------------------------------------------- 1 | {{! template-lint-disable no-autofocus-attribute }} 2 |
8 | {{#if (not-eq @type "checkbox")}} 9 | <@labelComponent /> 10 | {{/if}} 11 | 12 | 13 | {{#if (eq @type "select")}} 14 | 32 | {{else if (or (eq @type "radioGroup") (eq @type "radio-group"))}} 33 | 45 | {{else if (or (eq @type "checkboxGroup") (eq @type "checkbox-group"))}} 46 | 58 | {{else if (eq @type "checkbox")}} 59 | 72 | {{else if (eq @type "textarea")}} 73 | 89 | {{else if 90 | (and (eq @type "date") (not-eq this.dateComponent this.inputComponent)) 91 | }} 92 | 106 | {{else}} 107 | 122 | {{/if}} 123 | 124 | 125 | <@hintComponent /> 126 | <@errorComponent /> 127 |
-------------------------------------------------------------------------------- /addon/components/validated-input/render.js: -------------------------------------------------------------------------------- 1 | import Component from "@glimmer/component"; 2 | 3 | import passedOrDefault from "ember-validated-form/passed-or-default"; 4 | 5 | export default class RenderComponent extends Component { 6 | @passedOrDefault("types/checkbox-group") checkboxGroupComponent; 7 | @passedOrDefault("types/checkbox") checkboxComponent; 8 | @passedOrDefault("types/input") inputComponent; 9 | @passedOrDefault("types/radio-group") radioGroupComponent; 10 | @passedOrDefault("types/select") selectComponent; 11 | @passedOrDefault("types/textarea") textareaComponent; 12 | @passedOrDefault("types/date") dateComponent; 13 | } 14 | -------------------------------------------------------------------------------- /addon/components/validated-input/render/wrapper.hbs: -------------------------------------------------------------------------------- 1 | {{#if (macroCondition (macroGetOwnConfig "isUikit"))}} 2 |
{{yield}}
3 | {{else}} 4 | {{yield}} 5 | {{/if}} -------------------------------------------------------------------------------- /addon/components/validated-input/types/checkbox-group.hbs: -------------------------------------------------------------------------------- 1 | {{#each @options as |option i|}} 2 | {{#if (macroCondition (macroGetOwnConfig "isUikit"))}} 3 | {{#if (not-eq i 0)}}
{{/if}} 4 | 21 | {{else if (macroCondition (macroGetOwnConfig "isBootstrap"))}} 22 |
23 | 35 | 39 |
40 | {{else}} 41 | 54 | {{/if}} 55 | {{/each}} -------------------------------------------------------------------------------- /addon/components/validated-input/types/checkbox-group.js: -------------------------------------------------------------------------------- 1 | import { action } from "@ember/object"; 2 | import Component from "@glimmer/component"; 3 | 4 | export default class CheckboxGroupComponent extends Component { 5 | @action 6 | onUpdate(key, event) { 7 | event.preventDefault(); 8 | 9 | const value = new Set(this.args.value || []); 10 | 11 | value.delete(key) || value.add(key); 12 | 13 | this.args.update([...value]); 14 | this.args.setDirty(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /addon/components/validated-input/types/checkbox.hbs: -------------------------------------------------------------------------------- 1 | {{#if (macroCondition (macroGetOwnConfig "isUikit"))}} 2 | <@labelComponent 3 | class="{{if @isValid 'uk-text-success'}} {{if @isInvalid 'uk-text-danger'}}" 4 | > 5 | 15 | 16 | {{else if (macroCondition (macroGetOwnConfig "isBootstrap"))}} 17 |
18 | 30 | <@labelComponent class="custom-control-label" /> 31 |
32 | {{else}} 33 | 42 | <@labelComponent /> 43 | {{/if}} -------------------------------------------------------------------------------- /addon/components/validated-input/types/checkbox.js: -------------------------------------------------------------------------------- 1 | import { action } from "@ember/object"; 2 | import Component from "@glimmer/component"; 3 | 4 | export default class CheckboxComponent extends Component { 5 | @action 6 | onUpdate(event) { 7 | event.preventDefault(); 8 | 9 | this.args.update(event.target.checked); 10 | this.args.setDirty(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /addon/components/validated-input/types/input.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addon/components/validated-input/types/input.js: -------------------------------------------------------------------------------- 1 | import { action } from "@ember/object"; 2 | import Component from "@glimmer/component"; 3 | export default class InputComponent extends Component { 4 | @action 5 | onUpdate(event) { 6 | event.preventDefault(); 7 | 8 | this.args.update(event.target.value); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /addon/components/validated-input/types/radio-group.hbs: -------------------------------------------------------------------------------- 1 | {{#each @options as |option i|}} 2 | {{#if (macroCondition (macroGetOwnConfig "isUikit"))}} 3 | {{#if (not-eq i 0)}}
{{/if}} 4 | 22 | {{else if (macroCondition (macroGetOwnConfig "isBootstrap"))}} 23 |
24 | 37 | 41 |
42 | {{else}} 43 | 56 | {{/if}} 57 | {{/each}} -------------------------------------------------------------------------------- /addon/components/validated-input/types/radio-group.js: -------------------------------------------------------------------------------- 1 | import { action } from "@ember/object"; 2 | import Component from "@glimmer/component"; 3 | 4 | export default class RadioGroupComponent extends Component { 5 | @action 6 | onUpdate(value, event) { 7 | event.preventDefault(); 8 | 9 | this.args.update(value); 10 | this.args.setDirty(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /addon/components/validated-input/types/select.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addon/components/validated-input/types/select.js: -------------------------------------------------------------------------------- 1 | import EmberObject, { action, get } from "@ember/object"; 2 | import Component from "@glimmer/component"; 3 | 4 | /** 5 | * This component is heavily inspired by the ember-one-way-select addon. 6 | * https://github.com/DockYard/ember-one-way-select 7 | * Our implementation is slightly simpler, since it does not support 8 | * the block syntax for options. 9 | */ 10 | export default class SelectComponent extends Component { 11 | get hasPreGroupedOptions() { 12 | return ( 13 | this.args.options[0]?.groupName && 14 | Array.isArray(this.args.options[0]?.options) 15 | ); 16 | } 17 | 18 | get hasGrouping() { 19 | return this.hasPreGroupedOptions || this.args.groupLabelPath; 20 | } 21 | 22 | get normalizedOptions() { 23 | // normalize options to common data structure, only for rendering 24 | return this.args.options.map((opt) => this.normalize(opt)); 25 | } 26 | 27 | normalize(option) { 28 | if (typeof option !== "object") { 29 | return { id: option, label: option }; 30 | } 31 | const valuePath = this.args.optionValuePath ?? this.args.optionTargetPath; 32 | const labelPath = this.args.optionLabelPath; 33 | return { 34 | id: valuePath ? option[valuePath] : option.id, 35 | label: labelPath ? option[labelPath] : option.label, 36 | }; 37 | } 38 | 39 | get optionGroups() { 40 | const groupLabelPath = this.args.groupLabelPath; 41 | if (!groupLabelPath) { 42 | return this.args.options; 43 | } 44 | const groups = []; 45 | 46 | this.args.options.forEach((item) => { 47 | const label = get(item, groupLabelPath); 48 | 49 | if (label) { 50 | let group = groups.find((group) => group.groupName === label); 51 | 52 | if (!group) { 53 | group = EmberObject.create({ 54 | groupName: label, 55 | options: [], 56 | }); 57 | 58 | groups.push(group); 59 | } 60 | 61 | group.options.push(this.normalize(item)); 62 | } else { 63 | groups.push(item); 64 | } 65 | }); 66 | 67 | return groups; 68 | } 69 | 70 | findOption(target) { 71 | const targetPath = this.args.optionTargetPath; 72 | const valuePath = this.args.optionValuePath || targetPath; 73 | 74 | const getValue = (item) => { 75 | if (valuePath) { 76 | return String(item[valuePath]); 77 | } 78 | if (typeof item === "object") { 79 | return String(item.id); 80 | } 81 | return String(item); 82 | }; 83 | 84 | let options = this.args.options; 85 | 86 | //flatten pre grouped options 87 | if (this.hasPreGroupedOptions) { 88 | options = options.flatMap((group) => group.options); 89 | } 90 | 91 | // multi select 92 | if (this.args.multiple) { 93 | const selectedValues = Array.prototype.filter 94 | .call(target.options, (option) => option.selected) 95 | .map((option) => option.value); 96 | 97 | const foundOptions = options.filter((item) => { 98 | return selectedValues.includes(getValue(item)); 99 | }); 100 | if (targetPath) { 101 | return foundOptions.map((item) => item[targetPath]); 102 | } 103 | return foundOptions; 104 | } 105 | 106 | //single select 107 | const foundOption = options.find((item) => getValue(item) === target.value); 108 | 109 | // If @promptIsSelectable is set to true, foundOption in this case will be undefined. 110 | if (targetPath && foundOption) { 111 | return foundOption[targetPath]; 112 | } 113 | return foundOption; 114 | } 115 | 116 | @action 117 | onUpdate(event) { 118 | if (this.args.update) { 119 | this.args.update(this.findOption(event.target)); 120 | } 121 | } 122 | 123 | @action 124 | onBlur(event) { 125 | if (this.args.setDirty) { 126 | this.args.setDirty(event.target.value); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /addon/components/validated-input/types/textarea.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addon/components/validated-input/types/textarea.js: -------------------------------------------------------------------------------- 1 | import { action } from "@ember/object"; 2 | import Component from "@glimmer/component"; 3 | 4 | export default class TextareaComponent extends Component { 5 | @action 6 | onUpdate(event) { 7 | event.preventDefault(); 8 | 9 | this.args.update(event.target.value); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /addon/helpers/class-list.js: -------------------------------------------------------------------------------- 1 | import { helper } from "@ember/component/helper"; 2 | import { isEmpty } from "@ember/utils"; 3 | 4 | export default helper(function classList(classes) { 5 | return classes 6 | .filter((cls) => !isEmpty(cls)) 7 | .map((cls) => cls.trim()) 8 | .join(" "); 9 | }); 10 | -------------------------------------------------------------------------------- /addon/passed-or-default.js: -------------------------------------------------------------------------------- 1 | import { importSync, getOwnConfig } from "@embroider/macros"; 2 | import { ensureSafeComponent } from "@embroider/util"; 3 | 4 | export default function passedOrDefault(componentName) { 5 | return function (target, property) { 6 | return { 7 | get() { 8 | return ensureSafeComponent( 9 | this.args[property] ?? 10 | importSync(getOwnConfig()[componentName]).default, 11 | this, 12 | ); 13 | }, 14 | }; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /app/components/validated-button.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/components/validated-button"; 2 | -------------------------------------------------------------------------------- /app/components/validated-button/button.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/components/validated-button/button"; 2 | -------------------------------------------------------------------------------- /app/components/validated-form.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/components/validated-form"; 2 | -------------------------------------------------------------------------------- /app/components/validated-input.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/components/validated-input"; 2 | -------------------------------------------------------------------------------- /app/components/validated-input/error.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/components/validated-input/error"; 2 | -------------------------------------------------------------------------------- /app/components/validated-input/hint.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/components/validated-input/hint"; 2 | -------------------------------------------------------------------------------- /app/components/validated-input/label.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/components/validated-input/label"; 2 | -------------------------------------------------------------------------------- /app/components/validated-input/render.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/components/validated-input/render"; 2 | -------------------------------------------------------------------------------- /app/components/validated-input/render/wrapper.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/components/validated-input/render/wrapper"; 2 | -------------------------------------------------------------------------------- /app/components/validated-input/types/checkbox-group.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/components/validated-input/types/checkbox-group"; 2 | -------------------------------------------------------------------------------- /app/components/validated-input/types/checkbox.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/components/validated-input/types/checkbox"; 2 | -------------------------------------------------------------------------------- /app/components/validated-input/types/input.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/components/validated-input/types/input"; 2 | -------------------------------------------------------------------------------- /app/components/validated-input/types/radio-group.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/components/validated-input/types/radio-group"; 2 | -------------------------------------------------------------------------------- /app/components/validated-input/types/select.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/components/validated-input/types/select"; 2 | -------------------------------------------------------------------------------- /app/components/validated-input/types/textarea.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/components/validated-input/types/textarea"; 2 | -------------------------------------------------------------------------------- /app/helpers/class-list.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-validated-form/helpers/class-list"; 2 | -------------------------------------------------------------------------------- /blueprints/ember-validated-form/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | normalizeEntityName() { 3 | // this prevents an error when the entityName is 4 | // not specified (since that doesn't actually matter 5 | // to us 6 | }, 7 | 8 | afterInstall() { 9 | return this.addAddonsToProject({ 10 | packages: [ 11 | { name: "ember-changeset" }, 12 | { name: "ember-changeset-validations" }, 13 | { name: "ember-truth-helpers" }, 14 | ], 15 | }); 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /config/addon-docs.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | "use strict"; 3 | 4 | const AddonDocsConfig = require("ember-cli-addon-docs/lib/config"); 5 | 6 | module.exports = class extends AddonDocsConfig { 7 | // See https://ember-learn.github.io/ember-cli-addon-docs/latest/docs/deploying 8 | // for details on configuration you can override here. 9 | }; 10 | -------------------------------------------------------------------------------- /config/deploy.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | "use strict"; 3 | 4 | module.exports = function (deployTarget) { 5 | const ENV = { 6 | build: {}, 7 | // include other plugin configuration that applies to all deploy targets here 8 | }; 9 | 10 | if (deployTarget === "development") { 11 | ENV.build.environment = "development"; 12 | // configure other plugins for development deploy target here 13 | } 14 | 15 | if (deployTarget === "staging") { 16 | ENV.build.environment = "production"; 17 | // configure other plugins for staging deploy target here 18 | } 19 | 20 | if (deployTarget === "production") { 21 | ENV.build.environment = "production"; 22 | // configure other plugins for production deploy target here 23 | } 24 | 25 | // Note: if you need to build some configuration asynchronously, you can return 26 | // a promise that resolves with the ENV object instead of returning the 27 | // ENV object synchronously. 28 | return ENV; 29 | }; 30 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adfinis/ember-validated-form/4342564b7108d04900bffd0567780dcb5d72ed14/demo.gif -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const EmberAddon = require("ember-cli/lib/broccoli/ember-addon"); 4 | 5 | // Configuration for test scenarios 6 | const SCENARIO_CONFIGS = { 7 | THEME_DEFAULT: { theme: null }, 8 | THEME_UIKIT: { theme: "uikit" }, 9 | THEME_BOOTSTRAP: { theme: "bootstrap" }, 10 | CUSTOM_COMPONENTS: { 11 | defaults: { 12 | error: "dummy/components/x-custom-error", 13 | hint: "dummy/components/x-custom-hint", 14 | label: "dummy/components/x-custom-label", 15 | render: "dummy/components/x-custom-render", 16 | button: "dummy/components/x-custom-button", 17 | "types/checkbox": "dummy/components/x-custom-checkbox", 18 | "types/checkbox-group": "dummy/components/x-custom-checkbox-group", 19 | "types/input": "dummy/components/x-custom-input", 20 | "types/select": "dummy/components/x-custom-select", 21 | "types/radio-group": "dummy/components/x-custom-radio-group", 22 | "types/textarea": "dummy/components/x-custom-textarea", 23 | "types/date": "dummy/components/x-custom-date", 24 | }, 25 | }, 26 | }; 27 | 28 | module.exports = function (defaults) { 29 | const app = new EmberAddon(defaults, { 30 | snippetPaths: ["tests/dummy/app/snippets"], 31 | "ember-validated-form": { 32 | theme: "bootstrap", 33 | features: { 34 | scrollErrorIntoView: false, 35 | }, 36 | defaults: { 37 | hint: "dummy/components/permanent-custom-hint", 38 | }, 39 | ...(SCENARIO_CONFIGS[process.env.TEST_SCENARIO] ?? {}), 40 | }, 41 | "@embroider/macros": { 42 | setOwnConfig: { 43 | testScenario: process.env.TEST_SCENARIO ?? "THEME_DEFAULT", 44 | }, 45 | }, 46 | babel: { 47 | plugins: [ 48 | require.resolve("ember-concurrency/async-arrow-task-transform"), 49 | ], 50 | }, 51 | }); 52 | 53 | /* 54 | This build file specifies the options for the dummy test app of this 55 | addon, located in `/tests/dummy` 56 | This build file does *not* influence how the addon or the app using it 57 | behave. You most likely want to be modifying `./index.js` or app's build file 58 | */ 59 | 60 | const { maybeEmbroider } = require("@embroider/test-setup"); 61 | return maybeEmbroider(app, { 62 | skipBabel: [ 63 | { 64 | package: "qunit", 65 | }, 66 | ], 67 | }); 68 | }; 69 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | name: require("./package").name, 5 | 6 | included(...args) { 7 | this._super.included.apply(this, ...args); 8 | 9 | const app = this._findHost(this); 10 | 11 | const { 12 | theme = null, 13 | scrollErrorIntoView = false, 14 | defaults = {}, 15 | } = app.options["ember-validated-form"] ?? {}; 16 | 17 | // Theming options 18 | this.options["@embroider/macros"].setOwnConfig.isDefault = ![ 19 | "uikit", 20 | "bootstrap", 21 | ].includes(theme); 22 | this.options["@embroider/macros"].setOwnConfig.isUikit = theme === "uikit"; 23 | this.options["@embroider/macros"].setOwnConfig.isBootstrap = 24 | theme === "bootstrap"; 25 | 26 | // Features 27 | this.options["@embroider/macros"].setOwnConfig.scrollErrorIntoView = 28 | scrollErrorIntoView; 29 | 30 | // Component defaults 31 | this.options["@embroider/macros"].setOwnConfig.error = 32 | defaults.error ?? "ember-validated-form/components/validated-input/error"; 33 | this.options["@embroider/macros"].setOwnConfig.hint = 34 | defaults.hint ?? "ember-validated-form/components/validated-input/hint"; 35 | this.options["@embroider/macros"].setOwnConfig.label = 36 | defaults.label ?? "ember-validated-form/components/validated-input/label"; 37 | this.options["@embroider/macros"].setOwnConfig.render = 38 | defaults.render ?? 39 | "ember-validated-form/components/validated-input/render"; 40 | this.options["@embroider/macros"].setOwnConfig.button = 41 | defaults.button ?? 42 | "ember-validated-form/components/validated-button/button"; 43 | this.options["@embroider/macros"].setOwnConfig["types/checkbox-group"] = 44 | defaults["types/checkbox-group"] ?? 45 | "ember-validated-form/components/validated-input/types/checkbox-group"; 46 | this.options["@embroider/macros"].setOwnConfig["types/checkbox"] = 47 | defaults["types/checkbox"] ?? 48 | "ember-validated-form/components/validated-input/types/checkbox"; 49 | this.options["@embroider/macros"].setOwnConfig["types/input"] = 50 | defaults["types/input"] ?? 51 | "ember-validated-form/components/validated-input/types/input"; 52 | this.options["@embroider/macros"].setOwnConfig["types/radio-group"] = 53 | defaults["types/radio-group"] ?? 54 | "ember-validated-form/components/validated-input/types/radio-group"; 55 | this.options["@embroider/macros"].setOwnConfig["types/select"] = 56 | defaults["types/select"] ?? 57 | "ember-validated-form/components/validated-input/types/select"; 58 | this.options["@embroider/macros"].setOwnConfig["types/textarea"] = 59 | defaults["types/textarea"] ?? 60 | "ember-validated-form/components/validated-input/types/textarea"; 61 | this.options["@embroider/macros"].setOwnConfig["types/date"] = 62 | defaults["types/date"] ?? 63 | "ember-validated-form/components/validated-input/types/input"; 64 | }, 65 | 66 | options: { 67 | "@embroider/macros": { 68 | setOwnConfig: {}, 69 | }, 70 | }, 71 | }; 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-validated-form", 3 | "version": "7.0.1", 4 | "description": "Easily create forms with client-side validations", 5 | "keywords": [ 6 | "ember-addon", 7 | "forms", 8 | "form validation", 9 | "validation messages" 10 | ], 11 | "repository": "https://github.com/adfinis/ember-validated-form", 12 | "license": "MIT", 13 | "author": "Adfinis AG ", 14 | "directories": { 15 | "test": "tests" 16 | }, 17 | "homepage": "https://adfinis.github.io/ember-validated-form", 18 | "bugs": { 19 | "url": "https://github.com/adfinis/ember-validated-form/issues" 20 | }, 21 | "scripts": { 22 | "build": "ember build --environment=production", 23 | "lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"", 24 | "lint:css": "stylelint \"**/*.css\"", 25 | "lint:css:fix": "concurrently \"npm:lint:css -- --fix\"", 26 | "lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"", 27 | "lint:hbs": "ember-template-lint .", 28 | "lint:hbs:fix": "ember-template-lint . --fix", 29 | "lint:js": "eslint . --cache", 30 | "lint:js:fix": "eslint . --fix", 31 | "start": "ember serve", 32 | "test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"", 33 | "test:ember": "ember test", 34 | "test:ember-compatibility": "ember try:each", 35 | "prepare": "husky install" 36 | }, 37 | "dependencies": { 38 | "@babel/core": "^7.24.6", 39 | "@embroider/macros": "^1.16.1", 40 | "@embroider/util": "^1.13.0", 41 | "@glimmer/component": "^1.1.2", 42 | "@glimmer/tracking": "^1.1.2", 43 | "ember-changeset": "^4.1.2", 44 | "ember-changeset-validations": "^4.1.1", 45 | "ember-cli-babel": "^8.2.0", 46 | "ember-cli-htmlbars": "^6.3.0", 47 | "ember-truth-helpers": "^3.1.0" 48 | }, 49 | "devDependencies": { 50 | "@adfinis/eslint-config": "2.1.1", 51 | "@adfinis/semantic-release-config": "4.1.0", 52 | "@babel/eslint-parser": "7.26.5", 53 | "@babel/plugin-proposal-decorators": "7.24.1", 54 | "@ember/optional-features": "2.2.0", 55 | "@ember/string": "3.1.1", 56 | "@ember/test-helpers": "3.3.0", 57 | "@embroider/test-setup": "4.0.0", 58 | "@fortawesome/ember-fontawesome": "2.0.0", 59 | "@fortawesome/fontawesome-svg-core": "6.5.2", 60 | "@fortawesome/free-solid-svg-icons": "6.6.0", 61 | "broccoli-asset-rev": "3.0.0", 62 | "concurrently": "8.2.2", 63 | "ember-auto-import": "2.7.2", 64 | "ember-cli": "5.7.0", 65 | "ember-cli-addon-docs": "7.0.1", 66 | "ember-cli-clean-css": "3.0.0", 67 | "ember-cli-dependency-checker": "3.3.3", 68 | "ember-cli-deploy": "2.0.0", 69 | "ember-cli-deploy-build": "3.0.0", 70 | "ember-cli-deploy-git": "1.3.4", 71 | "ember-cli-deploy-git-ci": "1.0.1", 72 | "ember-cli-inject-live-reload": "2.1.0", 73 | "ember-cli-sri": "2.1.1", 74 | "ember-cli-terser": "4.0.2", 75 | "ember-cli-test-loader": "3.1.0", 76 | "ember-concurrency": "4.0.2", 77 | "ember-data": "5.3.3", 78 | "ember-flatpickr": "8.0.1", 79 | "ember-load-initializers": "2.1.2", 80 | "ember-qunit": "8.1.0", 81 | "ember-resolver": "11.0.1", 82 | "ember-source": "5.7.0", 83 | "ember-source-channel-url": "3.0.0", 84 | "ember-template-lint": "6.0.0", 85 | "ember-template-lint-plugin-prettier": "5.0.0", 86 | "ember-try": "3.0.0", 87 | "eslint": "8.57.0", 88 | "eslint-config-prettier": "9.1.0", 89 | "eslint-plugin-ember": "12.2.0", 90 | "eslint-plugin-import": "2.29.1", 91 | "eslint-plugin-n": "16.6.2", 92 | "eslint-plugin-prettier": "5.1.3", 93 | "eslint-plugin-qunit": "8.1.2", 94 | "flatpickr": "4.6.13", 95 | "husky": "9.0.11", 96 | "lint-staged": "15.2.2", 97 | "loader.js": "4.7.0", 98 | "prettier": "3.2.5", 99 | "qunit": "2.20.1", 100 | "qunit-dom": "3.0.0", 101 | "semantic-release": "23.1.1", 102 | "stylelint": "16.5.0", 103 | "stylelint-config-standard": "36.0.0", 104 | "stylelint-prettier": "5.0.0", 105 | "webpack": "5.97.1" 106 | }, 107 | "peerDependencies": { 108 | "ember-source": ">= 4.0.0" 109 | }, 110 | "packageManager": "pnpm@8.11.0", 111 | "pnpm": { 112 | "peerDependencyRules": { 113 | "allowedVersions": { 114 | "ember-source": "^5.0.0" 115 | } 116 | } 117 | }, 118 | "engines": { 119 | "node": ">= 18" 120 | }, 121 | "ember": { 122 | "edition": "octane" 123 | }, 124 | "ember-addon": { 125 | "configPath": "tests/dummy/config", 126 | "demoURL": "https://adfinis.github.io/ember-validated-form" 127 | }, 128 | "release": { 129 | "extends": "@adfinis/semantic-release-config" 130 | }, 131 | "lint-staged": { 132 | "*.js": "eslint --cache --fix", 133 | "*.hbs": "ember-template-lint --fix", 134 | "*.css": "stylelint --fix", 135 | "*.{json,md,yml}": "prettier --write" 136 | }, 137 | "commitlint": { 138 | "extends": [ 139 | "@commitlint/config-conventional" 140 | ] 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /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: [], 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/app.js: -------------------------------------------------------------------------------- 1 | import Application from "@ember/application"; 2 | import loadInitializers from "ember-load-initializers"; 3 | import Resolver from "ember-resolver"; 4 | 5 | import config from "dummy/config/environment"; 6 | 7 | export default class App extends Application { 8 | modulePrefix = config.modulePrefix; 9 | podModulePrefix = config.podModulePrefix; 10 | Resolver = Resolver; 11 | } 12 | 13 | loadInitializers(App, config.modulePrefix); 14 | -------------------------------------------------------------------------------- /tests/dummy/app/components/color-component.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/components/color-component.js: -------------------------------------------------------------------------------- 1 | import { htmlSafe } from "@ember/template"; 2 | import Component from "@glimmer/component"; 3 | 4 | export default class ColorComponent extends Component { 5 | get style() { 6 | return htmlSafe( 7 | `background-color: ${this.args.color.color}; font-size: 1rem; cursor: pointer;`, 8 | ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/dummy/app/components/custom-button.hbs: -------------------------------------------------------------------------------- 1 | {{! BEGIN-SNIPPET custom-button-component-template.hbs }} 2 | 4 | {{! END-SNIPPET }} -------------------------------------------------------------------------------- /tests/dummy/app/components/custom-error.hbs: -------------------------------------------------------------------------------- 1 | {{! template-lint-disable no-inline-styles }} 2 | {{! BEGIN-SNIPPET custom-error-component-template.hbs }} 3 |
4 |
5 | {{#each @errors as |error i|}} 6 | > Error #{{i}}: 7 | {{error}}
8 | {{/each}} 9 |
10 | {{! END-SNIPPET }} -------------------------------------------------------------------------------- /tests/dummy/app/components/custom-hint.hbs: -------------------------------------------------------------------------------- 1 | {{! BEGIN-SNIPPET custom-hint-component-template.hbs }} 2 | Hint: {{@hint}} 3 | {{! END-SNIPPET }} -------------------------------------------------------------------------------- /tests/dummy/app/components/custom-label.hbs: -------------------------------------------------------------------------------- 1 | {{! template-lint-disable no-inline-styles }} 2 | {{! BEGIN-SNIPPET custom-label-component-template.hbs }} 3 | 8 | {{! END-SNIPPET }} -------------------------------------------------------------------------------- /tests/dummy/app/components/favorite-colors.hbs: -------------------------------------------------------------------------------- 1 | {{! BEGIN-SNIPPET favorite-colors.hbs }} 2 |
3 | <@labelComponent /> 4 | 5 |
6 | {{#if @value}} 7 | {{@value.name}} 8 | 13 |
14 |
15 | {{/if}} 16 | 17 | {{#each @colors as |color|}} 18 | 22 | {{/each}} 23 |
24 | 25 | <@hintComponent /> 26 | <@errorComponent /> 27 |
28 | {{! END-SNIPPET }} -------------------------------------------------------------------------------- /tests/dummy/app/components/favorite-colors.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET favorite-colors.js 2 | import { action } from "@ember/object"; 3 | import Component from "@glimmer/component"; 4 | import { tracked } from "@glimmer/tracking"; 5 | 6 | export default class FavoriteColorsComponent extends Component { 7 | @tracked isShowingColors = false; 8 | 9 | @action 10 | onColorSelected(color) { 11 | this.isShowingColors = !this.isShowingColors; 12 | 13 | this.args.setDirty(); 14 | this.args.update(color); 15 | } 16 | 17 | @action 18 | toggle() { 19 | this.isShowingColors = !this.isShowingColors; 20 | } 21 | 22 | @action 23 | clearSelection() { 24 | this.args.update(null); 25 | } 26 | } 27 | // END-SNIPPET 28 | -------------------------------------------------------------------------------- /tests/dummy/app/components/flatpickr-wrapper.hbs: -------------------------------------------------------------------------------- 1 | {{! BEGIN-SNIPPET flatpickr-wrapper.hbs }} 2 | <@labelComponent /> 3 | 4 |
5 | 12 | 13 |
14 | 15 | <@hintComponent /> 16 | <@errorComponent /> 17 | {{! END-SNIPPET }} -------------------------------------------------------------------------------- /tests/dummy/app/components/permanent-custom-hint.hbs: -------------------------------------------------------------------------------- 1 | {{! BEGIN-SNIPPET permanent-custom-hint-component-template.hbs }} 2 | 3 | 4 | {{@hint}} 5 | 6 | {{! END-SNIPPET }} -------------------------------------------------------------------------------- /tests/dummy/app/components/x-custom-button.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/components/x-custom-checkbox-group.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/components/x-custom-checkbox.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/components/x-custom-date.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/components/x-custom-error.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/components/x-custom-hint.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/components/x-custom-input.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/components/x-custom-label.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/components/x-custom-radio-group.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/components/x-custom-render.hbs: -------------------------------------------------------------------------------- 1 | 2 | <@labelComponent /> 3 | <@hintComponent /> 4 | <@errorComponent /> 5 | 6 | {{#if (eq @type "select")}} 7 | 8 | {{else if (or (eq @type "radioGroup") (eq @type "radio-group"))}} 9 | 10 | {{else if (or (eq @type "checkboxGroup") (eq @type "checkbox-group"))}} 11 | 12 | {{else if (eq @type "checkbox")}} 13 | 14 | {{else if (eq @type "textarea")}} 15 | 16 | {{else if 17 | (and (eq @type "date") (not-eq this.dateComponent this.inputComponent)) 18 | }} 19 | 20 | {{else}} 21 | 22 | {{/if}} 23 | -------------------------------------------------------------------------------- /tests/dummy/app/components/x-custom-render.js: -------------------------------------------------------------------------------- 1 | import RenderComponent from "ember-validated-form/components/validated-input/render"; 2 | 3 | export default class extends RenderComponent {} 4 | -------------------------------------------------------------------------------- /tests/dummy/app/components/x-custom-select.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/components/x-custom-textarea.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/docs/components/validated-form.js: -------------------------------------------------------------------------------- 1 | import Controller from "@ember/controller"; 2 | import { action } from "@ember/object"; 3 | import { dropTask } from "ember-concurrency"; 4 | 5 | export default class extends Controller { 6 | // BEGIN-SNIPPET validated-form-task-controller.js 7 | @dropTask 8 | *submitTask(model) { 9 | yield model.save(); 10 | // ... more code to show success messages etc. 11 | } 12 | // END-SNIPPET 13 | 14 | // BEGIN-SNIPPET validated-form-action-controller.js 15 | @action 16 | async submitAction(model) { 17 | await model.save(); 18 | // ... more code to show success messages etc. 19 | } 20 | // END-SNIPPET 21 | } 22 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/index.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET quickstart-controller.js 2 | import Controller from "@ember/controller"; 3 | import { task, timeout } from "ember-concurrency"; 4 | 5 | import UserValidations from "dummy/validations/user"; 6 | 7 | export default class IndexController extends Controller { 8 | UserValidations = UserValidations; 9 | 10 | get colors() { 11 | return [ 12 | { name: "Red", color: "red" }, 13 | { name: "Green", color: "green" }, 14 | { name: "Blue", color: "blue" }, 15 | ]; 16 | } 17 | 18 | get countries() { 19 | return ["United States", "United Kingdom", "Switzerland", "Other"]; 20 | } 21 | 22 | get titles() { 23 | return [ 24 | { key: "mr", label: "Mr." }, 25 | { key: "mrs", label: "Mrs." }, 26 | { key: "ms", label: "Ms." }, 27 | { key: "prof", label: "Prof." }, 28 | { key: "dr", label: "Dr." }, 29 | ]; 30 | } 31 | 32 | get notifications() { 33 | return [ 34 | { key: "offers", label: "Offers" }, 35 | { key: "news", label: "News" }, 36 | { key: "features", label: "Features" }, 37 | ]; 38 | } 39 | 40 | @task 41 | *submit(model) { 42 | yield timeout(1000); 43 | yield model.save(); 44 | } 45 | } 46 | // END-SNIPPET 47 | -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adfinis/ember-validated-form/4342564b7108d04900bffd0567780dcb5d72ed14/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/dummy/app/locales/fr/translations.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET translations.js 2 | export default { 3 | some: { 4 | scope: { 5 | shapes: "les formes", 6 | triangle: "un triangle", 7 | square: "un carré", 8 | circle: "un cercle", 9 | }, 10 | }, 11 | }; 12 | // END-SNIPPET 13 | -------------------------------------------------------------------------------- /tests/dummy/app/models/user.js: -------------------------------------------------------------------------------- 1 | import Model, { attr } from "@ember-data/model"; 2 | 3 | export default class UserModel extends Model { 4 | @attr firstName; 5 | @attr lastName; 6 | @attr aboutMe; 7 | @attr country; 8 | @attr gender; 9 | @attr terms; 10 | @attr color; 11 | } 12 | -------------------------------------------------------------------------------- /tests/dummy/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from "ember-resolver"; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import AddonDocsRouter, { docsRoute } from "ember-cli-addon-docs/router"; 2 | 3 | import config from "./config/environment"; 4 | 5 | export default class Router extends AddonDocsRouter { 6 | location = config.locationType; 7 | rootURL = config.rootURL; 8 | } 9 | 10 | /* eslint-disable-next-line array-callback-return */ 11 | Router.map(function () { 12 | docsRoute(this, function () { 13 | this.route("usage"); 14 | this.route("quickstart"); 15 | this.route("configuration"); 16 | this.route("customization"); 17 | this.route("troubleshooting"); 18 | this.route("migration-v6"); 19 | 20 | this.route("components", function () { 21 | this.route("validated-form"); 22 | this.route("validated-input"); 23 | this.route("validated-button"); 24 | }); 25 | }); 26 | this.route("not-found", { path: "/*path" }); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/docs/components/validated-form.js: -------------------------------------------------------------------------------- 1 | import Route from "@ember/routing/route"; 2 | import { later } from "@ember/runloop"; 3 | import { Promise } from "rsvp"; 4 | 5 | export default class extends Route { 6 | // BEGIN-SNIPPET validated-form-route.js 7 | model() { 8 | return new (class { 9 | save() { 10 | return new Promise((resolve) => later(resolve, 1000)); 11 | } 12 | })(); 13 | } 14 | // END-SNIPPET 15 | } 16 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/index.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET quickstart-route.js 2 | import Route from "@ember/routing/route"; 3 | import { tracked } from "@glimmer/tracking"; 4 | 5 | class Model { 6 | @tracked saved = false; 7 | 8 | save() { 9 | this.saved = true; 10 | } 11 | } 12 | 13 | export default class IndexRoute extends Route { 14 | model() { 15 | return new Model(); 16 | } 17 | } 18 | // END-SNIPPET 19 | -------------------------------------------------------------------------------- /tests/dummy/app/services/store.js: -------------------------------------------------------------------------------- 1 | export { default } from "ember-data/store"; 2 | -------------------------------------------------------------------------------- /tests/dummy/app/snippets/config-custom-date.js: -------------------------------------------------------------------------------- 1 | const app = new EmberAddon(defaults, { 2 | // ... 3 | "ember-validated-form": { 4 | defaults: { 5 | "types/date": "myapp/components/flatpickr-wrapper", 6 | }, 7 | }, 8 | // ... 9 | }); 10 | -------------------------------------------------------------------------------- /tests/dummy/app/snippets/config-custom-hint.js: -------------------------------------------------------------------------------- 1 | const app = new EmberAddon(defaults, { 2 | // ... 3 | "ember-validated-form": { 4 | defaults: { 5 | hint: "myapp/components/permanent-custom-hint", 6 | }, 7 | }, 8 | // ... 9 | }); 10 | -------------------------------------------------------------------------------- /tests/dummy/app/snippets/config-defaults.js: -------------------------------------------------------------------------------- 1 | const app = new EmberAddon(defaults, { 2 | // ... 3 | "ember-validated-form": { 4 | defaults: { 5 | error: "myapp/components/x-my-error", 6 | hint: "myapp/components/x-my-hint", 7 | label: "myapp/components/x-my-label", 8 | render: "myapp/components/x-my-render", 9 | 10 | // button 11 | button: "myapp/components/x-my-button", 12 | 13 | // types 14 | "types/checkbox": "myapp/components/x-my-checkbox", 15 | "types/input": "myapp/components/x-my-input", 16 | "types/radio-group": "myapp/components/x-my-radio-group", 17 | "types/select": "myapp/components/x-my-select", 18 | "types/textarea": "myapp/components/x-my-textarea", 19 | "types/date": "myapp/components/x-my-date-picker", 20 | }, 21 | }, 22 | // ... 23 | }); 24 | -------------------------------------------------------------------------------- /tests/dummy/app/snippets/config-features.js: -------------------------------------------------------------------------------- 1 | const app = new EmberAddon(defaults, { 2 | // ... 3 | "ember-validated-form": { 4 | features: { 5 | scrollErrorIntoView: true, 6 | }, 7 | }, 8 | // ... 9 | }); 10 | -------------------------------------------------------------------------------- /tests/dummy/app/snippets/config-theme.js: -------------------------------------------------------------------------------- 1 | const app = new EmberAddon(defaults, { 2 | // ... 3 | "ember-validated-form": { 4 | theme: "bootstrap", 5 | }, 6 | // ... 7 | }); 8 | -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,400"); 2 | @import url("https://fonts.googleapis.com/css?family=Source+Code+Pro"); 3 | @import url("flatpickr/dist/flatpickr.css"); 4 | 5 | body { 6 | font-family: "Source Sans Pro", sans-serif; 7 | background-color: rgb(255 255 255); 8 | } 9 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/docs.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 26 | 30 | 31 | 32 | 33 |
34 |
35 | {{outlet}} 36 |
37 |
38 |
39 |
-------------------------------------------------------------------------------- /tests/dummy/app/templates/docs/components/validated-button.md: -------------------------------------------------------------------------------- 1 | # Validated button 2 | 3 | `` yields two kinds of button components: 4 | 5 | - ``: a submit button for the form 6 | - ``: a customizable button without HTML-form specific functionality. 7 | 8 | You can use them as a block style component `Test` if you don't want to pass the label as a 9 | property. 10 | 11 | Both take the following properties: 12 | 13 | **label ``** 14 | The label of the form button. 15 | 16 | **type ``** 17 | Type of the button. Default for submit: `submit` and for standard button: `button`. 18 | _Watch out:_ If you define `type=submit` then the `on-submit` handler of the form will be triggered. 19 | 20 | **disabled ``** 21 | Specifies if the button is disabled. 22 | 23 | **loading ``** 24 | Specifies if the button is loading. Default: Automatic integration of `ember-concurrency`. 25 | 26 | 27 | 28 | 29 | 30 | {{#let f.submit as |Submit|}} 31 | 32 | Save button in block style... 33 | {{/let}} 34 | {{if this.saved 'Saved!'}} 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | Further you can leverage the `{{f.button}}` component for custom actions. The model of the wrapping form component will get passed to the on-click handler as first argument. 43 | 44 | Custom buttons support the following additional options: 45 | 46 | **on-click ``** 47 | Passes an on-click function to the button component. 48 | 49 | **on-invalid-click ``** 50 | Passes a function which is triggered after clicking on the button and when the validation proved the contents to be invalid. 51 | 52 | **triggerValidations ``** 53 | Trigger the form validations when the button is clicked (or, more precisely: show all error messages). 54 | 55 | 56 | 57 | 58 | 59 | {{#let f.button as |CustomButton|}} 60 | 61 | Custom action button in block style... 62 | {{/let}} 63 | {{if this.triggered 'Action triggered!'}} 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/docs/components/validated-form.md: -------------------------------------------------------------------------------- 1 | # Validated form 2 | 3 | The `` component accepts the following arguments: 4 | 5 | **model ``** 6 | Changeset object containing the model that backs the form. 7 | 8 | **validateBeforeSubmit ``** 9 | Specifies whether to run validations on inputs before the form has been 10 | submitted. Defaults to true. 11 | 12 | **on-submit ``** 13 | Action, that is triggered on form submit if the changeset is valid. The 14 | changeset is passed as a parameter. If the action returns a promise, then any 15 | rendered submit buttons will have a customizable CSS class added and the yielded 16 | `loading` template parameter will be set. 17 | 18 | **on-invalid-submit ``** 19 | Action, that is triggered on form submit if the changset is invalid. The 20 | changeset is passed as a parameter. (Optional) 21 | 22 | **autocomplete ``** 23 | Binding to the [`
` `autocomplete` 24 | attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-autocomplete). 25 | 26 | When the submission of your form takes a little longer and your users are of 27 | the impatient kind, it is often necessary to disable the submit button to 28 | prevent the form from being submitted multiple times. This can be done using 29 | the `loading` template parameter: 30 | 31 | 32 | 33 | 34 | 35 | {{#let f.submit as |Submit|}} 36 | 37 | {{/let}} 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | It also works very well with [ember-concurrency](http://ember-concurrency.com/) tasks: 48 | 49 | 50 | 51 | 52 | 53 | {{#let f.submit as |Submit|}} 54 | 55 | {{/let}} 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/docs/components/validated-input.md: -------------------------------------------------------------------------------- 1 | ## Validated input 2 | 3 | `` yields an object, that contains the [contextual 4 | component](https://emberjs.com/blog/2016/01/15/ember-2-3-released.html#toc_contextual-components) 5 | `input`. All input fields share some common properties: 6 | 7 | **label ``** 8 | The label of the form field. 9 | 10 | **name ``** 11 | This is is the name of the model property this input is bound to. 12 | 13 | **inputName ``** 14 | The name attribute of the input element. If not passed it will default to the 15 | passed `name`. 16 | 17 | **hint ``** 18 | Additional explanatory text displayed below the input field. 19 | 20 | **type ``** 21 | Type of the form field (see supported field types below). Default: `text`. 22 | 23 | **disabled ``** 24 | Specifies if the input field is disabled. 25 | 26 | **required ``** 27 | If true, a "\*" is appended to the field"s label indicating that it is 28 | required. 29 | 30 | **value ``** 31 | Initial value of the form field. Default: model property defined by name. 32 | 33 | **validateBeforeSubmit ``** 34 | Specifies whether to run validations on this input before the form has been 35 | submitted. Defaults to the value set on the form. 36 | 37 | **on-update ``** 38 | Per default, the input elements are two-way-bound. If you want to implement 39 | custom update behavior, pass an action as `on-update`. The function receives 40 | two arguments: `update(value, changeset)`. 41 | 42 | **autocomplete ``** 43 | Binding to the [`` `autocomplete` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-autocomplete). 44 | 45 | The supported field types are `checkbox`, `checkboxGroup`, `radioGroup`, 46 | `select`, `textarea` and any type that can be specified on an `` element. 47 | This addon does not much more than translating `` to 48 | `` with the 49 | various other properties (`name`, `disabled`, etc.) and event handlers. 50 | 51 | However, some field types require extra parameters. The supported field types 52 | are listed below. 53 | 54 | ### Text input 55 | 56 | If no field type is specified, a simple `` is rendered. 57 | Other HTML5 text-like inputs like `email`, `number`, `search` require 58 | specifying their type. The element also supports the following options: 59 | 60 | - `placeholder` 61 | - `autofocus` 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | ### Textarea 77 | 78 | The textarea element also supports the following options: 79 | 80 | - `rows` and `cols` 81 | - `autofocus` 82 | - `placeholder` 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | ### Select 97 | 98 | The select element also supports the following options: 99 | 100 | - `value` 101 | - `options` 102 | - `optionLabelPath` 103 | - `optionValuePath` 104 | - `optionTargetPath` 105 | - `prompt` 106 | - `promptIsSelectable` 107 | - `groupLabelPath` 108 | 109 | 110 | 111 | 112 | 113 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | **Grouping** is supported in two ways: First by using the `groupLabelPath` property (e.g. `type` in th example below) or second by pre-grouped options in the form of: 130 | 131 | ```js 132 | [ 133 | { 134 | groupName: "one", 135 | options: [ 136 | { id: 1, label: "First", type: "group1" }, 137 | { id: 2, label: "Second", type: "group1" }, 138 | ], 139 | }, 140 | { 141 | groupName: "two", 142 | options: [{ id: 3, label: "Third", type: "group2" }], 143 | }, 144 | ]; 145 | ``` 146 | 147 | ### Checkbox 148 | 149 | This component renders an `` elements. 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | ### Radio button group 164 | 165 | This component renders a list of `` elements. 166 | 167 | 168 | 169 | 170 | 171 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 208 | 209 | ### Checkbox group 210 | 211 | This component renders a list of `` elements. 212 | 213 | 214 | 215 | 216 | 217 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/docs/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | ## Theme 4 | 5 | By setting the `theme` option, you can choose from a set of provided themes 6 | for `ember-uikit`. Currently we only support `bootstrap` and `uikit`. 7 | 8 | 9 | 10 | The idea for the future is to support various commonly used CSS frameworks 11 | like Material Design, Semantic UI or Bulma. **Pull Requests implementing such a theme 12 | are more than welcome!** 13 | 14 | ## Defaults 15 | 16 | If you want to specify a global custom component for yourself you can set the 17 | `defaults.[component]` property to the name of your custom component. 18 | 19 | 20 | 21 | For instance: 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ## Other features 37 | 38 | If you want to scroll the first invalid field into view, you can set the 39 | `scrollErrorIntoView` property to `true` (default: false). 40 | 41 | 42 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/docs/customization.md: -------------------------------------------------------------------------------- 1 | # Customization 2 | 3 | ## validated-input 4 | 5 | The whole `` appearance and behaviour can be customized to 6 | your specific needs. 7 | 8 | ### Input 9 | 10 | If the input element you need is not explicitly supported, you can easily 11 | integrate it with this addon by using the `component` template helper to pass 12 | a `renderComponent`: 13 | 14 | 15 | 16 | 17 | 18 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | **Arguments** 38 | 39 | - `<*>` **value** The current value of the field 40 | - `` **type** The type of the field 41 | - `` **inputId** The ID of the field (generated with `guidFor` from `@ember/object/internals`) 42 | - `` **options** The options for selects or radio groups 43 | - `` **name** The name of the field 44 | - `` **disabled** Whether the field is disabled 45 | - `` **autofocus** Whether to autofocus the field 46 | - `` **autocomplete** Whether to enable autocompletion on the field 47 | - `` **rows** The rows of the textarea 48 | - `` **cols** The cols of the textarea 49 | - `` **model** The model of the form 50 | - `` **isValid** Whether the form data is valid 51 | - `` **isInvalid** Whether the form data is invalid 52 | - `` **placeholder** The placeholder of the field 53 | - `` **class** The class for the render wrapper 54 | 55 | - `` **optionLabelPath** The property name of the label if objects are given as options 56 | - `` **optionValuePath** The property name of the value if objects are given as options 57 | - `` **optionTargetPath** Identical to `optionValuePath` but the return value will then be that property instead of the whole object 58 | - `` **prompt** Label of the prompt option (most likely an empty default option), will be omitted if left empty 59 | - `` **promptIsSelectable** Whether the prompt option is selectable 60 | - `` **multiple** Whether multiple options can be selected 61 | 62 | - `` **update** Action to update the value 63 | - `` **setDirty** Action to mark the field as dirty 64 | 65 | - `` **labelComponent** The label component 66 | - `` **hintComponent** The hint component 67 | - `` **errorComponent** The error component 68 | 69 | ### Label 70 | 71 | If you want to have a label on your input which renders something 72 | non-standard (for instance tooltips), then you can pass your custom component 73 | to the input in the following manner: 74 | 75 | _Note:_ When adding a custom component for input of type checkbox, one has to 76 | add `{{yield}}` inside the label. This is because, this kind of input renders 77 | inside a label tag. 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | **Arguments** 93 | 94 | - `` **label** The label of the field 95 | - `` **inputId** The id of the field, this is mostly used for the `for` attribute 96 | - `` **required** Whether the field is required 97 | - `` **isValid** Whether the field is valid 98 | - `` **isInvalid** Whether the field is invalid 99 | 100 | ### Hint 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | **Arguments** 116 | 117 | - `` **hint** The hint for the field 118 | 119 | ### Error 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | **Arguments** 135 | 136 | - `` **errors** The error messages of the field 137 | 138 | ### Date 139 | 140 | `ember-validated-form` has no default date picker implemented. If you specify an input 141 | with type `date`, a plain input with the HTML5 (depending on your browser) will 142 | be rendered. 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | This is on purpose due to there being many date picker components/addons 155 | available. And not every date picker fits every theme. 156 | 157 | If you would like to configure a custom date picker, configure a custom date 158 | component as specified in the _Defaults_ section of `Configuration`. 159 | 160 | 161 | 162 | 163 | 164 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | **Arguments** 181 | 182 | - `<*>` **value** The current value of the field 183 | - `` **update** Action to update the value 184 | - `` **inputId** The ID of the field (generated with `guidFor` from `@ember/object/internals`) 185 | - `` **placeholder** The placeholder of the field 186 | - `` **isValid** Whether the form data is valid 187 | - `` **isInvalid** Whether the form data is invalid 188 | - `` **setDirty** Action to mark the field as dirty 189 | - `` **name** The name of the field 190 | - `` **disabled** Whether the field is disabled 191 | - `` **autocomplete** Whether to enable autocompletion on the field 192 | 193 | ## validated-button 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | **Arguments** 209 | 210 | - `` **onclick** The onclick action passed to the button. Mostly, this will be the `submit` action of the form 211 | - `` **disabled** Whether the button is disabled 212 | - `` **label** The text of the button 213 | - `` **type** The type of the button 214 | - `` **class** The class of the button 215 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/docs/index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This [ember-cli](http://www.ember-cli.com) addon is based on the following 4 | excellent addons 5 | 6 | - [ember-changeset](https://github.com/DockYard/ember-changeset) 7 | - [ember-changeset-validations](https://github.com/DockYard/ember-changeset-validations/) 8 | 9 | and provides a handy out-of-the-box setup for user-friendly client-side 10 | validations, featuring 11 | 12 | - Hiding of validation errors until field has been interacted with (or submit button was pressed) 13 | - Preventing submit action until form is valid 14 | - Live-updating validation errors 15 | - Bootstrap integration 16 | - Loading class on submit button while async task is executed 17 | - Loading contextual template parameter set while async submit task is executed 18 | 19 | ## Why \*YAEFA? 20 | 21 | \*_Yet another ember form addon_ 22 | 23 | There are many [existing ember 24 | addons](https://emberobserver.com/categories/forms) with this style of API, 25 | the most prominent probably being 26 | [ember-form-for](https://github.com/martndemus/ember-form-for). With this 27 | addon, we want to: 28 | 29 | - focus on forms that require client-side validations 30 | - provide good user experience out of the box 31 | 32 | For more information, see this [blog 33 | post](https://adfinis.com/en/blog/form-validation-with-ember-js/). 34 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/docs/migration-v6.md: -------------------------------------------------------------------------------- 1 | # Migration to v6 2 | 3 | ## Config 4 | 5 | `ember-validated-form` is heavily based on dynamic component invokation which 6 | needed alot of changes in order to make it work with embroider. For the 7 | consumers of the addon, the only thing that changes is the static configuration. 8 | 9 | Since we switched from runtime configuration to build time configuration, the 10 | current configuration of `ember-validated-form` needs to be moved from 11 | `config/environment.js` to the `ember-cli-build.js` file: 12 | 13 | **Before:** 14 | 15 | ```js 16 | // config/environment.js 17 | var ENV = { 18 | // ... 19 | "ember-validated-form": { 20 | theme: "bootstrap", 21 | features: { 22 | scrollErrorIntoView: true, 23 | }, 24 | defaults: { 25 | error: "some-component", 26 | // ... 27 | }, 28 | }, 29 | // ... 30 | }; 31 | ``` 32 | 33 | **After:** 34 | 35 | ```js 36 | // ember-cli-build.js 37 | const app = new EmberAddon(defaults, { 38 | // ... 39 | "ember-validated-form": { 40 | theme: "bootstrap", 41 | features: { 42 | scrollErrorIntoView: true, 43 | }, 44 | defaults: { 45 | error: "myapp/components/some-component", 46 | // ... 47 | }, 48 | }, 49 | // ... 50 | }); 51 | ``` 52 | 53 | As you can see above, the values in the section `defaults` changed as well. 54 | Previously the value was just the name of the component used as default, since 55 | v6 this needs to be an importable path (which allows static analysis). 56 | 57 | ## Removed deprecations 58 | 59 | The `includeBlank` argument for validated inputs has been removed in favor of 60 | `prompt`. 61 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/docs/quickstart.md: -------------------------------------------------------------------------------- 1 | # Quickstart 2 | 3 | You'll find a basic example in [this 4 | twiddle](https://ember-twiddle.com/95b040c96b7dc60dc4d0bb2dc5f5de26?openFiles=templates.application.hbs%2C) -------------------------------------------------------------------------------- /tests/dummy/app/templates/docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | ## 'Proxy' is undefined (IE11) 4 | 5 | `ember-validated-form` installs `ember-changeset` and 6 | `ember-changeset-validations` **v3+** per default which relies heavily on 7 | `Proxy` which is not supported by IE11. If you need to support IE11 you'll 8 | need to [polyfill it](https://github.com/GoogleChrome/proxy-polyfill) or if 9 | you don't want to polyfill it, you can use `ember-changeset` and 10 | `ember-changeset-validations` **v2.x**. 11 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/docs/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | First, install the addon: 4 | 5 | ```bash 6 | $ ember install ember-validated-form 7 | ``` 8 | 9 | This will also install `ember-changeset` and `ember-changeset-validations`. 10 | After, you'll need to set up 11 | 12 | - a template containing your form elements 13 | - a validations file (see [ember-changeset-validations](https://github.com/poteto/ember-changeset-validations)) 14 | - a controller, route and/or component that provides your template with the validations and your model 15 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 |

Built with ♥ by 11 | Adfinis

12 | 13 |
14 | Ember Observer Score 22 | Test 30 | Code Style: Prettier 34 |
35 | 36 |
37 | 38 | 39 | {{! BEGIN-SNIPPET quickstart-template.hbs }} 40 | {{#if @model.saved}} 41 |
42 | Model was successfully saved! 43 |
44 | {{/if}} 45 | 46 | 51 | 58 | 59 | 60 | 61 | 62 | 63 | 70 | 71 | 80 | 81 | 82 | 83 | 89 | 90 | 96 | 97 | 103 | 104 | 108 | 109 | {{! END-SNIPPET }} 110 |
111 | 112 | 113 | 114 | 115 | 119 | 123 | 127 |
128 |
-------------------------------------------------------------------------------- /tests/dummy/app/templates/not-found.hbs: -------------------------------------------------------------------------------- 1 |
2 |

Not found

3 |

This page doesn't exist. Head home?

4 |
-------------------------------------------------------------------------------- /tests/dummy/app/validations/user.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET quickstart-validations.js 2 | import { 3 | validatePresence, 4 | validateLength, 5 | validateInclusion, 6 | } from "ember-changeset-validations/validators"; 7 | 8 | export default { 9 | firstName: [validatePresence(true), validateLength({ min: 3, max: 40 })], 10 | lastName: [validatePresence(true), validateLength({ min: 3, max: 40 })], 11 | aboutMe: [validateLength({ allowBlank: true, max: 200 })], 12 | country: [validatePresence(true)], 13 | title: [validatePresence(true)], 14 | terms: [ 15 | validateInclusion({ 16 | list: [true], 17 | message: "Please accept the terms and conditions!", 18 | }), 19 | ], 20 | color: [validatePresence(true)], 21 | }; 22 | // END-SNIPPET 23 | -------------------------------------------------------------------------------- /tests/dummy/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "5.7.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": ["--pnpm", "--no-welcome"] 14 | } 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tests/dummy/config/ember-try.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const embroiderTestSetup = require("@embroider/test-setup"); 4 | const getChannelURL = require("ember-source-channel-url"); 5 | 6 | const config = { 7 | usePnpm: true, 8 | scenarios: [ 9 | { 10 | name: "theme-default", 11 | env: { TEST_SCENARIO: "THEME_DEFAULT" }, 12 | }, 13 | { 14 | name: "theme-uikit", 15 | env: { TEST_SCENARIO: "THEME_UIKIT" }, 16 | }, 17 | { 18 | name: "theme-bootstrap", 19 | env: { TEST_SCENARIO: "THEME_BOOTSTRAP" }, 20 | }, 21 | { 22 | name: "custom-components", 23 | env: { TEST_SCENARIO: "CUSTOM_COMPONENTS" }, 24 | }, 25 | ], 26 | }; 27 | 28 | function buildLTSScenario(version) { 29 | return { 30 | ...config, 31 | scenarios: config.scenarios.map((scenario) => ({ 32 | ...scenario, 33 | name: `ember-lts-${version} (${scenario.name})`, 34 | npm: { 35 | devDependencies: { "ember-source": `~${version}.0` }, 36 | }, 37 | })), 38 | }; 39 | } 40 | 41 | async function buildURLScenario(release) { 42 | const emberSourceURL = await getChannelURL(release); 43 | 44 | return { 45 | ...config, 46 | scenarios: config.scenarios.map((scenario) => ({ 47 | ...scenario, 48 | name: `ember-${release} (${scenario.name})`, 49 | npm: { 50 | devDependencies: { "ember-source": emberSourceURL }, 51 | }, 52 | })), 53 | }; 54 | } 55 | 56 | function buildEmbroiderScenario(type) { 57 | const embroiderScenario = embroiderTestSetup[type](); 58 | 59 | return { 60 | ...config, 61 | scenarios: config.scenarios.map((scenario) => ({ 62 | ...scenario, 63 | ...embroiderScenario, 64 | env: { ...embroiderScenario.env, ...scenario.env }, 65 | name: `${embroiderScenario.name} (${scenario.name})`, 66 | })), 67 | }; 68 | } 69 | 70 | module.exports = function () { 71 | const scenario = process.env.EMBER_SCENARIO; 72 | 73 | if (/^ember-lts-\d+\.\d+/.test(scenario)) { 74 | return buildLTSScenario(scenario.replace(/^ember-lts-/, "")); 75 | } else if (/^ember-(release|beta|canary)$/.test(scenario)) { 76 | return buildURLScenario(scenario.replace(/^ember-/, "")); 77 | } else if (/^embroider-(safe|optimized)$/.test(scenario)) { 78 | // convert embroider-xy to embroiderXy 79 | const embroiderScenario = scenario 80 | .split("-") 81 | .map((part, i) => 82 | i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1), 83 | ) 84 | .join(""); 85 | 86 | return buildEmbroiderScenario(embroiderScenario); 87 | } 88 | 89 | return config; 90 | }; 91 | -------------------------------------------------------------------------------- /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 | historySupportMiddleware: true, 10 | EmberENV: { 11 | EXTEND_PROTOTYPES: false, 12 | FEATURES: { 13 | // Here you can enable experimental features on an ember canary build 14 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 15 | }, 16 | }, 17 | 18 | APP: { 19 | // Here you can pass flags/options to your application instance 20 | // when it is created 21 | }, 22 | }; 23 | 24 | if (environment === "development") { 25 | // ENV.APP.LOG_RESOLVER = true; 26 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 27 | // ENV.APP.LOG_TRANSITIONS = true; 28 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 29 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 30 | } 31 | 32 | if (environment === "test") { 33 | // Testem prefers this... 34 | ENV.locationType = "none"; 35 | 36 | // keep test console output quieter 37 | ENV.APP.LOG_ACTIVE_GENERATION = false; 38 | ENV.APP.LOG_VIEW_LOOKUPS = false; 39 | 40 | ENV.APP.rootElement = "#ember-testing"; 41 | ENV.APP.autoboot = false; 42 | } 43 | 44 | if (environment === "production") { 45 | ENV.rootURL = "/ADDON_DOCS_ROOT_URL/"; 46 | } 47 | 48 | return ENV; 49 | }; 50 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /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); // 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/helpers/scenarios.js: -------------------------------------------------------------------------------- 1 | import { getOwnConfig } from "@embroider/macros"; 2 | import { test } from "qunit"; 3 | 4 | const testScenario = getOwnConfig()?.testScenario; 5 | 6 | function buildScenarioTester(env) { 7 | return function (name, ...args) { 8 | if (testScenario === env) { 9 | return test(`${name} [${env}]`, ...args); 10 | } 11 | }; 12 | } 13 | 14 | export const testUikit = buildScenarioTester("THEME_UIKIT"); 15 | export const testBootstrap = buildScenarioTester("THEME_BOOTSTRAP"); 16 | export const testDefault = buildScenarioTester("THEME_DEFAULT"); 17 | export const testCustomComponents = buildScenarioTester("CUSTOM_COMPONENTS"); 18 | -------------------------------------------------------------------------------- /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/integration/components/validated-button-test.js: -------------------------------------------------------------------------------- 1 | import { render } from "@ember/test-helpers"; 2 | import { setupRenderingTest } from "ember-qunit"; 3 | import hbs from "htmlbars-inline-precompile"; 4 | import { module } from "qunit"; 5 | 6 | import { testDefault } from "dummy/tests/helpers/scenarios"; 7 | 8 | module("Integration | Component | validated button", function (hooks) { 9 | setupRenderingTest(hooks); 10 | 11 | hooks.beforeEach(function () { 12 | this.noop = () => {}; 13 | }); 14 | 15 | testDefault("it renders a button with a label", async function (assert) { 16 | await render(hbs``); 17 | assert.dom("button").hasText("Test"); 18 | }); 19 | 20 | testDefault("it renders a button with block style", async function (assert) { 21 | await render( 22 | hbs`Test`, 23 | ); 24 | assert.dom("button").hasText("Test"); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/integration/components/validated-button/button-test.js: -------------------------------------------------------------------------------- 1 | import { render } from "@ember/test-helpers"; 2 | import { setupRenderingTest } from "ember-qunit"; 3 | import hbs from "htmlbars-inline-precompile"; 4 | import { module } from "qunit"; 5 | 6 | import { 7 | testDefault, 8 | testUikit, 9 | testBootstrap, 10 | } from "dummy/tests/helpers/scenarios"; 11 | 12 | module("Integration | Component | validated-button/button", function (hooks) { 13 | setupRenderingTest(hooks); 14 | 15 | hooks.beforeEach(function () { 16 | this.noop = () => {}; 17 | }); 18 | 19 | testDefault("it renders", async function (assert) { 20 | await render(hbs``); 21 | 22 | assert.dom("button").exists(); 23 | 24 | await render(hbs` 25 | Test 26 | `); 27 | 28 | assert.dom("button").hasText("Test"); 29 | }); 30 | 31 | testUikit("it renders", async function (assert) { 32 | await render( 33 | hbs``, 34 | ); 35 | 36 | assert.dom("button").hasText("Test"); 37 | assert.dom("button").hasClass("uk-button"); 38 | assert.dom("button").hasClass("uk-button-default"); 39 | }); 40 | 41 | testUikit("it renders in block style", async function (assert) { 42 | await render(hbs` 43 | Test 44 | `); 45 | 46 | assert.dom("button").hasText("Test"); 47 | }); 48 | 49 | testUikit( 50 | "it renders a primary button for submit buttons", 51 | async function (assert) { 52 | await render( 53 | hbs``, 54 | ); 55 | 56 | assert.dom("button").hasClass("uk-button-primary"); 57 | }, 58 | ); 59 | 60 | testBootstrap("it renders", async function (assert) { 61 | await render( 62 | hbs``, 63 | ); 64 | 65 | assert.dom("button").hasText("Test"); 66 | assert.dom("button").hasClass("btn"); 67 | assert.dom("button").hasClass("btn-default"); 68 | }); 69 | 70 | testBootstrap("it renders in block style", async function (assert) { 71 | await render(hbs` 72 | Test 73 | `); 74 | 75 | assert.dom("button").hasText("Test"); 76 | }); 77 | 78 | testBootstrap( 79 | "it renders a primary button for submit buttons", 80 | async function (assert) { 81 | await render( 82 | hbs``, 83 | ); 84 | 85 | assert.dom("button").hasClass("btn-primary"); 86 | }, 87 | ); 88 | }); 89 | -------------------------------------------------------------------------------- /tests/integration/components/validated-form-defaults-test.js: -------------------------------------------------------------------------------- 1 | import { render } from "@ember/test-helpers"; 2 | import { setupRenderingTest } from "ember-qunit"; 3 | import hbs from "htmlbars-inline-precompile"; 4 | import { module } from "qunit"; 5 | 6 | import { testCustomComponents } from "dummy/tests/helpers/scenarios"; 7 | 8 | module("Integration | Component | validated form defaults", function (hooks) { 9 | setupRenderingTest(hooks); 10 | 11 | testCustomComponents("renders custom components", async function (assert) { 12 | assert.expect(4); 13 | 14 | this.set("model", { error: { test1: { validation: ["Error"] } } }); 15 | 16 | await render(hbs` 17 | 25 | `); 26 | 27 | assert.dom("custom-render").exists(); 28 | assert.dom("custom-label").exists(); 29 | assert.dom("custom-hint").exists(); 30 | assert.dom("custom-error").exists(); 31 | }); 32 | 33 | testCustomComponents( 34 | "renders custom button components", 35 | async function (assert) { 36 | assert.expect(1); 37 | 38 | await render(hbs` 39 | 40 | `); 41 | 42 | assert.dom("custom-button").exists(); 43 | }, 44 | ); 45 | 46 | testCustomComponents( 47 | "renders custom type components", 48 | async function (assert) { 49 | assert.expect(7); 50 | 51 | await render(hbs` 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | `); 60 | 61 | assert.dom("custom-checkbox").exists(); 62 | assert.dom("custom-checkbox-group").exists(); 63 | assert.dom("custom-input").exists(); 64 | assert.dom("custom-radio-group").exists(); 65 | assert.dom("custom-select").exists(); 66 | assert.dom("custom-textarea").exists(); 67 | assert.dom("custom-date").exists(); 68 | }, 69 | ); 70 | }); 71 | -------------------------------------------------------------------------------- /tests/integration/components/validated-form-test.js: -------------------------------------------------------------------------------- 1 | import EmberObject from "@ember/object"; 2 | import { run } from "@ember/runloop"; 3 | import { render, click, blur, fillIn, focus } from "@ember/test-helpers"; 4 | import { validateLength } from "ember-changeset-validations/validators"; 5 | import { setupRenderingTest } from "ember-qunit"; 6 | import hbs from "htmlbars-inline-precompile"; 7 | import { module } from "qunit"; 8 | import { defer } from "rsvp"; 9 | 10 | import { testDefault, testBootstrap } from "dummy/tests/helpers/scenarios"; 11 | import UserValidations from "dummy/validations/user"; 12 | 13 | module("Integration | Component | validated form", function (hooks) { 14 | setupRenderingTest(hooks); 15 | 16 | testDefault("it renders simple inputs", async function (assert) { 17 | await render(hbs` 18 | 19 | `); 20 | 21 | assert.dom("form label").hasText("First name"); 22 | assert.dom("form input").hasAttribute("type", "text"); 23 | }); 24 | 25 | testDefault("it renders textareas", async function (assert) { 26 | await render(hbs` 27 | 28 | `); 29 | 30 | assert.dom("form label").hasText("my label"); 31 | assert.dom("form textarea").exists({ count: 1 }); 32 | }); 33 | 34 | testBootstrap("it renders a radio group", async function (assert) { 35 | this.set("buttonGroupData", { 36 | options: [ 37 | { key: "1", label: "Option 1" }, 38 | { key: "2", label: "Option 2" }, 39 | { key: "3", label: "Option 3" }, 40 | ], 41 | }); 42 | 43 | await render(hbs` 44 | 50 | `); 51 | 52 | assert.dom('input[type="radio"]').exists({ count: 3 }); 53 | assert.dom("label:nth-of-type(1)").hasText("Options"); 54 | assert.dom("div:nth-of-type(1) > input + label").hasText("Option 1"); 55 | assert.dom("div:nth-of-type(2) > input + label").hasText("Option 2"); 56 | assert.dom("div:nth-of-type(3) > input + label").hasText("Option 3"); 57 | assert.dom("div > input + label").exists({ count: 3 }); 58 | assert.dom('input[type="radio"][value="1"]').exists(); 59 | assert.dom('input[type="radio"][value="2"]').exists(); 60 | assert.dom('input[type="radio"][value="3"]').exists(); 61 | }); 62 | 63 | testDefault("it renders submit buttons", async function (assert) { 64 | this.set("stub", function () {}); 65 | 66 | await render(hbs` 67 | 68 | 69 | `); 70 | 71 | assert.dom("form button").hasAttribute("type", "submit"); 72 | assert.dom("form button").hasText("Save!"); 73 | }); 74 | 75 | testDefault("it renders an always-showing hint", async function (assert) { 76 | await render(hbs` 77 | 78 | `); 79 | 80 | assert.dom("input + div").doesNotExist(); 81 | assert.dom("input + small").exists({ count: 1 }); 82 | assert.dom("input + small").hasText("Not your middle name!"); 83 | 84 | const hintId = this.element.querySelector("input + small").id; 85 | 86 | assert.dom("input").hasAria("describedby", hintId); 87 | }); 88 | 89 | testDefault( 90 | "does not render a

tag for buttons if no callbacks were passed", 91 | async function (assert) { 92 | await render(hbs` 93 | 94 | `); 95 | 96 | assert.dom("form > p").doesNotExist(); 97 | }, 98 | ); 99 | 100 | testDefault("it supports default button labels", async function (assert) { 101 | this.set("stub", function () {}); 102 | 103 | await render(hbs` 104 | 105 | `); 106 | 107 | assert.dom("form button[type=submit]").hasText("Save"); 108 | }); 109 | 110 | testBootstrap( 111 | "it performs basic validations on submit", 112 | async function (assert) { 113 | this.set("submit", function () {}); 114 | this.set("UserValidations", UserValidations); 115 | 116 | run(() => { 117 | this.set( 118 | "model", 119 | EmberObject.create({ 120 | firstName: "x", 121 | }), 122 | ); 123 | }); 124 | 125 | await render(hbs` 130 | 131 | 132 | `); 133 | 134 | assert.dom("span.invalid-feedback").doesNotExist(); 135 | assert.dom("input").doesNotHaveAria("invalid"); 136 | 137 | await click("button"); 138 | 139 | assert.dom("input").hasValue("x"); 140 | assert.dom("span.invalid-feedback").exists({ count: 1 }); 141 | assert 142 | .dom("span.invalid-feedback") 143 | .hasText("First name must be between 3 and 40 characters"); 144 | 145 | const errorId = this.element.querySelector("span.invalid-feedback").id; 146 | 147 | assert.dom("input").hasAria("invalid", "true"); 148 | assert.dom("input").hasAria("describedby", errorId); 149 | }, 150 | ); 151 | 152 | testBootstrap( 153 | "it shows error message for custom buttons if (and only if) triggerValidations is passed", 154 | async function (assert) { 155 | this.set("UserValidations", UserValidations); 156 | 157 | run(() => { 158 | this.set( 159 | "model", 160 | EmberObject.create({ 161 | firstName: "x", 162 | }), 163 | ); 164 | }); 165 | 166 | this.set("triggerValidations", false); 167 | 168 | await render(hbs` 169 | 170 | 171 | `); 172 | 173 | assert.dom("span.invalid-feedback").doesNotExist(); 174 | await click("button"); 175 | assert.dom("span.invalid-feedback").doesNotExist(); 176 | 177 | this.set("triggerValidations", true); 178 | await click("button"); 179 | 180 | assert.dom("input").hasValue("x"); 181 | assert.dom("span.invalid-feedback").exists({ count: 1 }); 182 | assert 183 | .dom("span.invalid-feedback") 184 | .hasText("First name must be between 3 and 40 characters"); 185 | }, 186 | ); 187 | 188 | testDefault( 189 | "it calls on-invalid-submit after submit if changeset is invalid", 190 | async function (assert) { 191 | let invalidSubmitCalled; 192 | this.set("invalidSubmit", function () { 193 | invalidSubmitCalled = true; 194 | }); 195 | this.set("UserValidations", UserValidations); 196 | 197 | run(() => { 198 | this.set( 199 | "model", 200 | EmberObject.create({ 201 | firstName: "x", 202 | }), 203 | ); 204 | }); 205 | 206 | await render(hbs` 211 | 212 | 213 | `); 214 | 215 | await click("button"); 216 | 217 | assert.true(invalidSubmitCalled); 218 | }, 219 | ); 220 | 221 | testDefault( 222 | "it does not call on-invalid-submit after submit if changeset is valid", 223 | async function (assert) { 224 | let invalidSubmitCalled; 225 | let submitCalled; 226 | this.set("submit", function () { 227 | submitCalled = true; 228 | }); 229 | this.set("invalidSubmit", function () { 230 | invalidSubmitCalled = true; 231 | }); 232 | 233 | run(() => { 234 | this.set("model", EmberObject.create({})); 235 | }); 236 | 237 | await render(hbs` 243 | 244 | 245 | `); 246 | 247 | await click("button"); 248 | 249 | assert.notOk(invalidSubmitCalled); 250 | assert.true(submitCalled); 251 | }, 252 | ); 253 | 254 | testDefault( 255 | "it performs validation and calls onInvalidClick function on custom buttons", 256 | async function (assert) { 257 | assert.expect(5); 258 | 259 | this.set("onClick", function () { 260 | assert.step("onClick"); 261 | }); 262 | this.set("onInvalidClick", function (model) { 263 | assert.step("onInvalidClick"); 264 | assert.strictEqual(model.firstName, "x"); 265 | }); 266 | this.set("SimpleValidations", { 267 | firstName: [validateLength({ min: 3, max: 40 })], 268 | }); 269 | 270 | run(() => { 271 | this.set( 272 | "model", 273 | EmberObject.create({ 274 | firstName: "x", 275 | }), 276 | ); 277 | }); 278 | 279 | await render(hbs` 280 | 281 | 285 | `); 286 | 287 | await click("button"); 288 | assert.verifySteps(["onInvalidClick"]); 289 | await fillIn("input[name=firstName]", "Some name"); 290 | await click("button"); 291 | assert.verifySteps(["onClick"]); 292 | }, 293 | ); 294 | 295 | testBootstrap( 296 | "it performs basic validations on focus out", 297 | async function (assert) { 298 | this.set("submit", function () {}); 299 | this.set("UserValidations", UserValidations); 300 | 301 | run(() => { 302 | this.set("model", EmberObject.create({})); 303 | }); 304 | 305 | await render(hbs` 310 | 311 | 312 | `); 313 | 314 | assert.dom("input + div").doesNotExist(); 315 | 316 | await focus("input"); 317 | await blur("input"); 318 | 319 | assert.dom("span.invalid-feedback").exists({ count: 1 }); 320 | assert.dom("span.invalid-feedback").hasText("First name can't be blank"); 321 | }, 322 | ); 323 | 324 | testBootstrap( 325 | "it skips basic validations on focus out with validateBeforeSubmit=false set on the form", 326 | async function (assert) { 327 | this.set("submit", function () {}); 328 | this.set("UserValidations", UserValidations); 329 | 330 | run(() => { 331 | this.set("model", EmberObject.create({})); 332 | }); 333 | 334 | await render(hbs` 340 | 341 | 342 | `); 343 | 344 | assert.dom("span.invalid-feedback").doesNotExist(); 345 | 346 | await focus("input"); 347 | await blur("input"); 348 | 349 | assert.dom("span.invalid-feedback").doesNotExist(); 350 | 351 | await click("button"); 352 | 353 | assert.dom("span.invalid-feedback").exists({ count: 1 }); 354 | }, 355 | ); 356 | 357 | testDefault( 358 | "it skips basic validations on focus out with validateBeforeSubmit=false set on the input", 359 | async function (assert) { 360 | this.set("submit", function () {}); 361 | this.set("UserValidations", UserValidations); 362 | 363 | run(() => { 364 | this.set("model", EmberObject.create({})); 365 | }); 366 | 367 | await render(hbs` 372 | 377 | `); 378 | 379 | assert.dom("input + div").doesNotExist(); 380 | 381 | await focus("input"); 382 | await blur("input"); 383 | 384 | assert.dom("input + div").doesNotExist(); 385 | }, 386 | ); 387 | 388 | testDefault( 389 | "on-submit can be an action returning a promise", 390 | async function (assert) { 391 | const deferred = defer(); 392 | 393 | this.set("submit", () => deferred.promise); 394 | 395 | run(() => { 396 | this.set("model", EmberObject.create({})); 397 | }); 398 | 399 | await render(hbs` 404 | 405 | `); 406 | 407 | assert.dom("button").doesNotHaveClass("loading"); 408 | 409 | await click("button"); 410 | 411 | assert.dom("button").hasClass("loading"); 412 | 413 | run(() => deferred.resolve()); 414 | 415 | assert.dom("button").doesNotHaveClass("loading"); 416 | }, 417 | ); 418 | 419 | testDefault( 420 | "on-submit can be an action returning a non-promise", 421 | async function (assert) { 422 | this.set("submit", () => undefined); 423 | 424 | run(() => { 425 | this.set("model", EmberObject.create({})); 426 | }); 427 | 428 | await render(hbs` 433 | 434 | `); 435 | 436 | assert.dom("button").doesNotHaveClass("loading"); 437 | 438 | await click("button"); 439 | 440 | assert.dom("button").doesNotHaveClass("loading"); 441 | }, 442 | ); 443 | 444 | testDefault("it yields the loading state", async function (assert) { 445 | const deferred = defer(); 446 | 447 | this.set("submit", () => deferred.promise); 448 | 449 | run(() => { 450 | this.set("model", EmberObject.create({})); 451 | }); 452 | 453 | await render(hbs` 458 | {{#if f.loading}} 459 | loading... 460 | {{/if}} 461 | 462 | `); 463 | assert.dom("span.loading").doesNotExist(); 464 | 465 | await click("button"); 466 | 467 | assert.dom("span.loading").exists(); 468 | 469 | run(() => deferred.resolve()); 470 | 471 | assert.dom("span.loading").doesNotExist(); 472 | }); 473 | 474 | testDefault( 475 | "it handles being removed from the DOM during sync submit", 476 | async function (assert) { 477 | this.set("show", true); 478 | 479 | this.set("submit", () => { 480 | this.set("show", false); 481 | }); 482 | 483 | run(() => { 484 | this.set("model", EmberObject.create({})); 485 | }); 486 | 487 | await render(hbs`{{#if this.show}} 488 | 493 | {{#if f.loading}} 494 | loading... 495 | {{/if}} 496 | 497 | 498 | {{/if}}`); 499 | 500 | await click("button"); 501 | assert.ok(true); 502 | }, 503 | ); 504 | 505 | testDefault( 506 | "it handles being removed from the DOM during async submit", 507 | async function (assert) { 508 | this.set("show", true); 509 | const deferred = defer(); 510 | 511 | this.set("submit", () => { 512 | return deferred.promise.then(() => { 513 | this.set("show", false); 514 | }); 515 | }); 516 | 517 | run(() => { 518 | this.set("model", EmberObject.create({})); 519 | }); 520 | 521 | await render(hbs`{{#if this.show}} 522 | 527 | {{#if f.loading}} 528 | loading... 529 | {{/if}} 530 | 531 | 532 | {{/if}}`); 533 | 534 | await click("button"); 535 | run(() => deferred.resolve()); 536 | assert.ok(true); 537 | }, 538 | ); 539 | 540 | testDefault("it binds the autocomplete attribute", async function (assert) { 541 | await render(hbs``); 542 | 543 | assert.dom("form").hasAttribute("autocomplete", "off"); 544 | }); 545 | }); 546 | -------------------------------------------------------------------------------- /tests/integration/components/validated-input-test.js: -------------------------------------------------------------------------------- 1 | import { render, click, fillIn, settled } from "@ember/test-helpers"; 2 | import Changeset from "ember-changeset"; 3 | import { hbs } from "ember-cli-htmlbars"; 4 | import { setupRenderingTest } from "ember-qunit"; 5 | import { module } from "qunit"; 6 | 7 | import { testDefault, testBootstrap } from "dummy/tests/helpers/scenarios"; 8 | 9 | module("Integration | Component | validated input", function (hooks) { 10 | setupRenderingTest(hooks); 11 | 12 | testDefault( 13 | "it renders simple text inputs with correct name", 14 | async function (assert) { 15 | await render(hbs``); 16 | 17 | assert.dom("input").hasAttribute("type", "text"); 18 | assert.dom("input").hasAttribute("name", "bar"); 19 | }, 20 | ); 21 | 22 | testDefault("it renders email input", async function (assert) { 23 | await render(hbs``); 24 | 25 | assert.dom("input").hasAttribute("type", "email"); 26 | }); 27 | 28 | testDefault("it renders tel input", async function (assert) { 29 | await render(hbs``); 30 | 31 | assert.dom("input").hasAttribute("type", "tel"); 32 | }); 33 | 34 | testDefault("it renders disabled inputs", async function (assert) { 35 | await render(hbs``); 36 | 37 | assert.dom("input").isDisabled(); 38 | }); 39 | 40 | testDefault("it renders inputs with placeholder", async function (assert) { 41 | await render(hbs``); 42 | 43 | assert.dom("input").hasAttribute("placeholder", "foo"); 44 | }); 45 | 46 | testDefault("it renders inputs with value", async function (assert) { 47 | await render(hbs``); 48 | 49 | assert.dom("input").hasValue("foo"); 50 | }); 51 | 52 | testDefault("it renders inputs with model", async function (assert) { 53 | this.set("model", new Changeset({ firstName: "Max" })); 54 | 55 | await render( 56 | hbs``, 57 | ); 58 | 59 | assert.dom("input").hasValue("Max"); 60 | }); 61 | 62 | testDefault("it calls on-update if given", async function (assert) { 63 | this.set("model", new Changeset({ firstName: "Max" })); 64 | this.set("update", (value, changeset) => { 65 | changeset.set("firstName", value.toUpperCase()); 66 | }); 67 | await render( 68 | hbs``, 73 | ); 74 | 75 | await fillIn("input", "foo"); 76 | 77 | assert.dom("input").hasValue("FOO"); 78 | }); 79 | 80 | testDefault( 81 | "it renders inputs with value even if model is defined", 82 | async function (assert) { 83 | this.set("model", new Changeset({ firstName: "Max" })); 84 | 85 | await render( 86 | hbs``, 87 | ); 88 | 89 | assert.dom("input").hasValue("foobar"); 90 | }, 91 | ); 92 | 93 | testDefault( 94 | "it renders textarea inputs with correct name", 95 | async function (assert) { 96 | await render(hbs``); 97 | 98 | assert.dom("textarea").hasAttribute("name", "bar"); 99 | }, 100 | ); 101 | 102 | testDefault("it renders disabled textareas", async function (assert) { 103 | await render(hbs``); 104 | 105 | assert.dom("textarea").isDisabled(); 106 | }); 107 | 108 | testDefault("it renders textareas with placeholder", async function (assert) { 109 | await render(hbs``); 110 | 111 | assert.dom("textarea").hasAttribute("placeholder", "foo"); 112 | }); 113 | 114 | testDefault("it renders textareas with value", async function (assert) { 115 | await render(hbs``); 116 | 117 | assert.dom("textarea").hasValue("foo"); 118 | }); 119 | 120 | testDefault("it renders textareas with model", async function (assert) { 121 | this.set("model", new Changeset({ firstName: "Max" })); 122 | 123 | await render( 124 | hbs``, 125 | ); 126 | 127 | assert.dom("textarea").hasValue("Max"); 128 | }); 129 | 130 | testDefault( 131 | "it renders textareas autocomplete attribute", 132 | async function (assert) { 133 | await render( 134 | hbs``, 135 | ); 136 | 137 | assert.dom("textarea").hasAttribute("autocomplete", "given-name"); 138 | }, 139 | ); 140 | 141 | testDefault( 142 | "it renders input autocomplete attribute", 143 | async function (assert) { 144 | await render( 145 | hbs``, 150 | ); 151 | 152 | assert.dom("input").hasAttribute("autocomplete", "new-password"); 153 | }, 154 | ); 155 | 156 | testDefault("it renders the block if provided", async function (assert) { 157 | await render( 158 | hbs` 159 |

160 |
`, 161 | ); 162 | 163 | assert.dom("#custom-input").exists(); 164 | }); 165 | 166 | testDefault( 167 | "it yields the value provided to the block", 168 | async function (assert) { 169 | await render( 170 | hbs` 171 | 172 | `, 173 | ); 174 | 175 | assert.dom("input").hasValue("my-value"); 176 | }, 177 | ); 178 | 179 | testDefault( 180 | "it yields the name from the model as value", 181 | async function (assert) { 182 | this.set("model", new Changeset({ firstName: "Max" })); 183 | 184 | await render( 185 | hbs` 186 | 187 | `, 188 | ); 189 | 190 | assert.dom("input").hasValue("Max"); 191 | }, 192 | ); 193 | 194 | testDefault( 195 | "it yields the value as value if both model and value is provided", 196 | async function (assert) { 197 | this.set("model", new Changeset({ firstName: "Max" })); 198 | 199 | await render( 200 | hbs` 206 | 207 | `, 208 | ); 209 | 210 | assert.dom("input").hasValue("Other Value"); 211 | }, 212 | ); 213 | 214 | testDefault("it yields the provided name", async function (assert) { 215 | await render( 216 | hbs` 217 | 218 | `, 219 | ); 220 | 221 | assert.dom("input").hasAttribute("name", "foobar"); 222 | }); 223 | 224 | testDefault("it yields the model", async function (assert) { 225 | this.set("model", new Changeset({ firstName: "Max" })); 226 | 227 | await render( 228 | hbs` 229 | 230 | `, 231 | ); 232 | 233 | assert.dom("input").hasValue("Max"); 234 | }); 235 | 236 | testDefault( 237 | "it yields an action for updating the model", 238 | async function (assert) { 239 | const model = new Changeset({ firstName: "Max" }); 240 | this.set("model", model); 241 | 242 | await render( 243 | hbs` 244 | 245 | `, 246 | ); 247 | 248 | await click("button"); 249 | 250 | assert.strictEqual(model.get("firstName"), "Merlin"); 251 | }, 252 | ); 253 | 254 | testBootstrap( 255 | "it yields an action marking the input as dirty", 256 | async function (assert) { 257 | this.set("model", { error: { test: { validation: ["Error"] } } }); 258 | 259 | await render( 260 | hbs` 261 | 262 | `, 263 | ); 264 | 265 | assert.dom("span.invalid-feedback").doesNotExist(); 266 | 267 | await click("button"); 268 | 269 | assert.dom("span.invalid-feedback").exists(); 270 | }, 271 | ); 272 | 273 | testDefault("it yields the input id to the block", async function (assert) { 274 | await render( 275 | hbs` 276 | 277 | `, 278 | ); 279 | 280 | const label = this.element.querySelector("label"); 281 | const input = this.element.querySelector("input"); 282 | assert.strictEqual(label.getAttribute("for"), input.getAttribute("id")); 283 | }); 284 | 285 | testDefault( 286 | "it can change the value from outside the input", 287 | async function (assert) { 288 | this.set("model", new Changeset({ firstName: "Max" })); 289 | 290 | await render( 291 | hbs``, 292 | ); 293 | 294 | assert.dom("input").hasValue("Max"); 295 | 296 | this.set("model.firstName", "Hans"); 297 | 298 | assert.dom("input").hasValue("Hans"); 299 | }, 300 | ); 301 | 302 | testDefault("it can overwrite the input name", async function (assert) { 303 | this.set("model", new Changeset({ firstName: "Max" })); 304 | 305 | await render( 306 | hbs``, 311 | ); 312 | 313 | assert.dom("input").hasValue("Max"); 314 | assert.dom("input").hasAttribute("name", "testFirstName"); 315 | }); 316 | 317 | testDefault("it updates _val on nested fields", async function (assert) { 318 | this.set("model", new Changeset({ nested: { name: "Max" } })); 319 | 320 | await render( 321 | hbs`{{this.model.nested.name}} 322 | 323 | {{Input.value}} 324 | `, 325 | ); 326 | 327 | assert.dom("#raw").hasText("Max"); 328 | assert.dom("#_val").hasText("Max"); 329 | 330 | this.model.set("nested.name", "Tom"); 331 | await settled(); 332 | 333 | assert.dom("#raw").hasText("Tom"); 334 | assert.dom("#_val").hasText("Tom"); 335 | }); 336 | }); 337 | -------------------------------------------------------------------------------- /tests/integration/components/validated-input/error-test.js: -------------------------------------------------------------------------------- 1 | import { render } from "@ember/test-helpers"; 2 | import { setupRenderingTest } from "ember-qunit"; 3 | import hbs from "htmlbars-inline-precompile"; 4 | import { module } from "qunit"; 5 | 6 | import { 7 | testDefault, 8 | testUikit, 9 | testBootstrap, 10 | } from "dummy/tests/helpers/scenarios"; 11 | 12 | module("Integration | Component | validated-input/error", function (hooks) { 13 | setupRenderingTest(hooks); 14 | 15 | testDefault("it renders", async function (assert) { 16 | this.set("errors", ["foo", "bar", "baz"]); 17 | 18 | await render(hbs``); 19 | 20 | assert.dom("span").hasText("foo, bar, baz"); 21 | }); 22 | 23 | testUikit("it renders", async function (assert) { 24 | this.set("errors", ["foo", "bar", "baz"]); 25 | 26 | await render(hbs``); 27 | 28 | assert.dom("small").hasClass("uk-text-danger"); 29 | assert.dom("small").hasText("foo, bar, baz"); 30 | }); 31 | 32 | testBootstrap("it renders", async function (assert) { 33 | this.set("errors", ["foo", "bar", "baz"]); 34 | 35 | await render(hbs``); 36 | 37 | assert.dom("span").hasClass("invalid-feedback"); 38 | assert.dom("span").hasClass("d-block"); 39 | assert.dom("span").hasText("foo, bar, baz"); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /tests/integration/components/validated-input/hint-test.js: -------------------------------------------------------------------------------- 1 | import { render } from "@ember/test-helpers"; 2 | import { setupRenderingTest } from "ember-qunit"; 3 | import hbs from "htmlbars-inline-precompile"; 4 | import { module } from "qunit"; 5 | 6 | import { 7 | testDefault, 8 | testUikit, 9 | testBootstrap, 10 | } from "dummy/tests/helpers/scenarios"; 11 | 12 | module("Integration | Component | validated-input/hint", function (hooks) { 13 | setupRenderingTest(hooks); 14 | 15 | testDefault("it renders", async function (assert) { 16 | await render(hbs``); 17 | 18 | assert.dom("small").hasText("Test"); 19 | }); 20 | 21 | testUikit("it renders", async function (assert) { 22 | await render(hbs``); 23 | 24 | assert.dom("small").hasClass("uk-text-muted"); 25 | assert.dom("small").hasText("Test"); 26 | }); 27 | 28 | testBootstrap("it renders", async function (assert) { 29 | await render(hbs``); 30 | 31 | assert.dom("small").hasClass("form-text"); 32 | assert.dom("small").hasClass("text-muted"); 33 | assert.dom("small").hasText("Test"); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /tests/integration/components/validated-input/label-test.js: -------------------------------------------------------------------------------- 1 | import { render } from "@ember/test-helpers"; 2 | import { setupRenderingTest } from "ember-qunit"; 3 | import hbs from "htmlbars-inline-precompile"; 4 | import { module } from "qunit"; 5 | 6 | import { 7 | testDefault, 8 | testUikit, 9 | testBootstrap, 10 | } from "dummy/tests/helpers/scenarios"; 11 | 12 | module("Integration | Component | validated-input/label", function (hooks) { 13 | setupRenderingTest(hooks); 14 | 15 | testDefault("it renders", async function (assert) { 16 | await render(hbs``); 17 | 18 | assert.dom("label").hasText("Test"); 19 | }); 20 | 21 | testUikit("it renders", async function (assert) { 22 | await render(hbs``); 23 | 24 | assert.dom("label").hasClass("uk-form-label"); 25 | assert.dom("label").hasText("Test"); 26 | }); 27 | 28 | testBootstrap("it renders", async function (assert) { 29 | await render(hbs``); 30 | 31 | assert.dom("label").hasText("Test"); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/integration/components/validated-input/render-test.js: -------------------------------------------------------------------------------- 1 | import { render } from "@ember/test-helpers"; 2 | import { setupRenderingTest } from "ember-qunit"; 3 | import hbs from "htmlbars-inline-precompile"; 4 | import { module } from "qunit"; 5 | 6 | import { 7 | testDefault, 8 | testUikit, 9 | testBootstrap, 10 | } from "dummy/tests/helpers/scenarios"; 11 | 12 | module("Integration | Component | validated-input/render", function (hooks) { 13 | setupRenderingTest(hooks); 14 | 15 | testDefault("it renders", async function (assert) { 16 | await render(hbs``); 23 | 24 | assert.dom("input[type=text]").exists(); 25 | assert.dom("input[type=text]").hasAttribute("name", "test"); 26 | assert.dom("label").hasText("Test"); 27 | }); 28 | 29 | testUikit("it renders", async function (assert) { 30 | await render(hbs``); 37 | 38 | assert.dom(".uk-margin").exists(); 39 | assert.dom(".uk-margin > .uk-form-label").exists(); 40 | assert.dom(".uk-margin > .uk-form-controls").exists(); 41 | 42 | assert.dom("input[type=text].uk-input").exists(); 43 | assert.dom("input[type=text].uk-input").hasAttribute("name", "test"); 44 | assert.dom("label").hasText("Test"); 45 | }); 46 | 47 | testBootstrap("it renders", async function (assert) { 48 | await render(hbs``); 55 | 56 | assert.dom(".form-group").exists(); 57 | 58 | assert.dom("input[type=text].form-control").exists(); 59 | assert.dom("input[type=text].form-control").hasAttribute("name", "test"); 60 | assert.dom("label").hasText("Test"); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /tests/integration/components/validated-input/render/wrapper-test.js: -------------------------------------------------------------------------------- 1 | import { render } from "@ember/test-helpers"; 2 | import { hbs } from "ember-cli-htmlbars"; 3 | import { module } from "qunit"; 4 | 5 | import { setupRenderingTest } from "dummy/tests/helpers"; 6 | import { 7 | testDefault, 8 | testUikit, 9 | testBootstrap, 10 | } from "dummy/tests/helpers/scenarios"; 11 | 12 | module( 13 | "Integration | Component | validated-input/render/wrapper", 14 | function (hooks) { 15 | setupRenderingTest(hooks); 16 | 17 | testDefault("it renders", async function (assert) { 18 | await render( 19 | hbs`Test`, 20 | ); 21 | 22 | assert.dom(this.element).hasText("Test"); 23 | }); 24 | 25 | testBootstrap("it renders", async function (assert) { 26 | await render( 27 | hbs`Test`, 28 | ); 29 | 30 | assert.dom(this.element).hasText("Test"); 31 | }); 32 | 33 | testUikit("it renders", async function (assert) { 34 | await render( 35 | hbs`Test`, 36 | ); 37 | 38 | assert.dom("div.uk-form-controls").hasText("Test"); 39 | }); 40 | }, 41 | ); 42 | -------------------------------------------------------------------------------- /tests/integration/components/validated-input/types/checkbox-group-test.js: -------------------------------------------------------------------------------- 1 | import { click, render } from "@ember/test-helpers"; 2 | import { setupRenderingTest } from "ember-qunit"; 3 | import hbs from "htmlbars-inline-precompile"; 4 | import { module } from "qunit"; 5 | 6 | import { 7 | testDefault, 8 | testUikit, 9 | testBootstrap, 10 | } from "dummy/tests/helpers/scenarios"; 11 | 12 | module( 13 | "Integration | Component | validated-input/types/checkbox-group", 14 | function (hooks) { 15 | setupRenderingTest(hooks); 16 | 17 | testDefault("it renders", async function (assert) { 18 | this.set("options", [ 19 | { key: 1, label: 1 }, 20 | { key: 2, label: 2 }, 21 | ]); 22 | 23 | await render(hbs``); 27 | 28 | assert.dom("input[type=checkbox]").exists({ count: 2 }); 29 | }); 30 | 31 | testDefault("it can select multiple values", async function (assert) { 32 | this.options = [ 33 | { key: 1, label: 1 }, 34 | { key: 2, label: 2 }, 35 | ]; 36 | this.value = []; 37 | 38 | await render(hbs``); 44 | 45 | await click('input[value="1"]'); 46 | await click('input[value="2"]'); 47 | 48 | assert.deepEqual(this.value, [1, 2]); 49 | assert.true(this.dirty); 50 | }); 51 | 52 | testUikit("it renders", async function (assert) { 53 | this.set("options", [ 54 | { 55 | key: "opt1", 56 | label: "Option 1", 57 | }, 58 | { 59 | key: "opt2", 60 | label: "Option 2", 61 | }, 62 | ]); 63 | 64 | await render(hbs``); 68 | 69 | assert.dom("label > input").exists(); 70 | assert.dom("input").hasClass("uk-checkbox"); 71 | assert.dom("label").hasClass("uk-form-label"); 72 | }); 73 | 74 | testBootstrap("it renders", async function (assert) { 75 | await render(hbs``); 82 | 83 | assert.dom("div.custom-control.custom-checkbox").exists(); 84 | assert.dom("input").hasClass("custom-control-input"); 85 | assert.dom("label").hasClass("custom-control-label"); 86 | }); 87 | }, 88 | ); 89 | -------------------------------------------------------------------------------- /tests/integration/components/validated-input/types/checkbox-test.js: -------------------------------------------------------------------------------- 1 | import { render } from "@ember/test-helpers"; 2 | import { setupRenderingTest } from "ember-qunit"; 3 | import hbs from "htmlbars-inline-precompile"; 4 | import { module } from "qunit"; 5 | 6 | import { 7 | testDefault, 8 | testUikit, 9 | testBootstrap, 10 | } from "dummy/tests/helpers/scenarios"; 11 | 12 | module( 13 | "Integration | Component | validated-input/types/checkbox", 14 | function (hooks) { 15 | setupRenderingTest(hooks); 16 | 17 | testDefault("it renders", async function (assert) { 18 | await render(hbs``); 22 | 23 | assert.dom("input[type=checkbox]").exists(); 24 | }); 25 | 26 | testUikit("it renders", async function (assert) { 27 | await render(hbs``); 31 | 32 | assert.dom("label > input").exists(); 33 | assert.dom("input").hasClass("uk-checkbox"); 34 | assert.dom("label").hasClass("uk-form-label"); 35 | }); 36 | 37 | testBootstrap("it renders", async function (assert) { 38 | await render(hbs``); 42 | 43 | assert.dom("div.custom-control.custom-checkbox").exists(); 44 | assert.dom("input").hasClass("custom-control-input"); 45 | assert.dom("label").hasClass("custom-control-label"); 46 | }); 47 | }, 48 | ); 49 | -------------------------------------------------------------------------------- /tests/integration/components/validated-input/types/input-test.js: -------------------------------------------------------------------------------- 1 | import { render } from "@ember/test-helpers"; 2 | import { setupRenderingTest } from "ember-qunit"; 3 | import hbs from "htmlbars-inline-precompile"; 4 | import { module } from "qunit"; 5 | 6 | import { 7 | testDefault, 8 | testUikit, 9 | testBootstrap, 10 | } from "dummy/tests/helpers/scenarios"; 11 | 12 | module( 13 | "Integration | Component | validated-input/types/input", 14 | function (hooks) { 15 | setupRenderingTest(hooks); 16 | 17 | testDefault("it renders", async function (assert) { 18 | await render(hbs``); 22 | 23 | assert.dom("input").exists(); 24 | }); 25 | 26 | testUikit("it renders", async function (assert) { 27 | await render(hbs``); 31 | 32 | assert.dom("input").hasClass("uk-input"); 33 | }); 34 | 35 | testBootstrap("it renders", async function (assert) { 36 | await render(hbs``); 40 | 41 | assert.dom("input").hasClass("form-control"); 42 | }); 43 | }, 44 | ); 45 | -------------------------------------------------------------------------------- /tests/integration/components/validated-input/types/radio-group-test.js: -------------------------------------------------------------------------------- 1 | import { render } from "@ember/test-helpers"; 2 | import { setupRenderingTest } from "ember-qunit"; 3 | import hbs from "htmlbars-inline-precompile"; 4 | import { module } from "qunit"; 5 | 6 | import { 7 | testDefault, 8 | testUikit, 9 | testBootstrap, 10 | } from "dummy/tests/helpers/scenarios"; 11 | 12 | module( 13 | "Integration | Component | validated-input/types/radio-group", 14 | function (hooks) { 15 | setupRenderingTest(hooks); 16 | 17 | testDefault("it renders", async function (assert) { 18 | this.set("options", [ 19 | { key: 1, label: 1 }, 20 | { key: 2, label: 2 }, 21 | ]); 22 | 23 | await render(hbs``); 27 | 28 | assert.dom("input[type=radio]").exists({ count: 2 }); 29 | }); 30 | 31 | testUikit("it renders", async function (assert) { 32 | this.set("options", [ 33 | { 34 | key: "opt1", 35 | label: "Option 1", 36 | }, 37 | { 38 | key: "opt2", 39 | label: "Option 2", 40 | }, 41 | ]); 42 | 43 | await render(hbs``); 47 | 48 | assert.dom("label > input").exists({ count: 2 }); 49 | assert.dom("input").hasClass("uk-radio"); 50 | assert.dom("label").hasClass("uk-form-label"); 51 | }); 52 | 53 | testBootstrap("it renders", async function (assert) { 54 | this.set("options", [ 55 | { 56 | key: "opt1", 57 | label: "Option 1", 58 | }, 59 | { 60 | key: "opt2", 61 | label: "Option 2", 62 | }, 63 | ]); 64 | 65 | await render(hbs``); 69 | 70 | assert.dom("div.custom-control.custom-radio").exists({ count: 2 }); 71 | assert.dom("input").hasClass("custom-control-input"); 72 | assert.dom("label").hasClass("custom-control-label"); 73 | }); 74 | }, 75 | ); 76 | -------------------------------------------------------------------------------- /tests/integration/components/validated-input/types/select-test.js: -------------------------------------------------------------------------------- 1 | import { render, select } from "@ember/test-helpers"; 2 | import { setupRenderingTest } from "ember-qunit"; 3 | import hbs from "htmlbars-inline-precompile"; 4 | import { module } from "qunit"; 5 | 6 | import { 7 | testDefault, 8 | testUikit, 9 | testBootstrap, 10 | } from "dummy/tests/helpers/scenarios"; 11 | 12 | module( 13 | "Integration | Component | validated-input/types/select", 14 | function (hooks) { 15 | setupRenderingTest(hooks); 16 | 17 | testDefault("it renders", async function (assert) { 18 | this.set("options", [ 19 | { id: 1, label: 1 }, 20 | { id: 2, label: 2 }, 21 | ]); 22 | 23 | await render( 24 | hbs``, 25 | ); 26 | 27 | assert.dom("select").exists(); 28 | assert.dom("option").exists({ count: 2 }); 29 | assert.dom("option:first-child").hasProperty("selected", true); 30 | }); 31 | 32 | testDefault( 33 | "it supports plain (non-object) options", 34 | async function (assert) { 35 | assert.expect(6); 36 | this.set("options", ["foo", "bar"]); 37 | 38 | this.set("update", (value) => { 39 | assert.strictEqual(value, "bar"); 40 | }); 41 | await render( 42 | hbs``, 46 | ); 47 | 48 | assert.dom("select").exists(); 49 | assert.dom("option").exists({ count: 2 }); 50 | assert.dom("option:first-child").hasProperty("selected", true); 51 | await select("select", "bar"); 52 | assert.dom("option:first-child").hasProperty("selected", false); 53 | assert.dom("option:last-child").hasProperty("selected", true); 54 | }, 55 | ); 56 | 57 | testDefault( 58 | "it works with solitary optionTargetPath property", 59 | async function (assert) { 60 | assert.expect(2); 61 | this.set("options", [ 62 | { key: 111, label: "firstOption" }, 63 | { key: 222, label: "secondOption" }, 64 | ]); 65 | 66 | this.set("update", (value) => { 67 | assert.strictEqual(value, 222); 68 | }); 69 | 70 | await render( 71 | hbs``, 76 | ); 77 | 78 | assert.dom("option:first-child").hasText("firstOption"); 79 | await select("select", "222"); 80 | }, 81 | ); 82 | 83 | testDefault( 84 | "it renders option groups via groupLabelPath", 85 | async function (assert) { 86 | this.set("options", [ 87 | { key: 1, label: 1, group: "one" }, 88 | { key: 2, label: 2, group: "two" }, 89 | ]); 90 | 91 | await render( 92 | hbs``, 98 | ); 99 | 100 | assert.dom("select").exists(); 101 | assert.dom("optgroup[label='one']").exists({ count: 1 }); 102 | assert.dom("optgroup[label='two']").exists({ count: 1 }); 103 | assert.dom("optgroup:first-child option:first-child").hasText("1"); 104 | assert.dom("optgroup:last-child option:first-child").hasValue("2"); 105 | }, 106 | ); 107 | 108 | testDefault( 109 | "it renders option groups pre grouped options", 110 | async function (assert) { 111 | this.set("options", [ 112 | { 113 | groupName: "one", 114 | options: [ 115 | { id: 1, label: "First", type: "group1" }, 116 | { id: 2, label: "Second", type: "group1" }, 117 | ], 118 | }, 119 | { 120 | groupName: "two", 121 | options: [{ id: 3, label: "Third", type: "group2" }], 122 | }, 123 | ]); 124 | 125 | await render( 126 | hbs``, 131 | ); 132 | 133 | assert.dom("select").exists(); 134 | assert.dom("optgroup[label='one']").exists({ count: 1 }); 135 | assert.dom("optgroup:first-child option:first-child").hasText("First"); 136 | assert.dom("optgroup[label='two']").exists({ count: 1 }); 137 | assert.dom("optgroup:last-child option:first-child").hasText("Third"); 138 | assert.dom("optgroup:last-child option:first-child").hasValue("3"); 139 | }, 140 | ); 141 | 142 | testDefault("it selects the pre-defined value", async function (assert) { 143 | this.set("value", "2"); 144 | this.set("options", [ 145 | { key: "1", label: 1 }, 146 | { key: "2", label: 2 }, 147 | ]); 148 | 149 | await render( 150 | hbs``, 156 | ); 157 | 158 | assert.dom("select").hasValue(this.options[1].key); 159 | assert.dom("option:first-child").hasProperty("selected", false); 160 | assert.dom("option:last-child").hasProperty("selected", true); 161 | }); 162 | 163 | testDefault("prompt is present and disabled", async function (assert) { 164 | this.set("options", [ 165 | { key: 1, label: 1 }, 166 | { key: 2, label: 2 }, 167 | ]); 168 | 169 | await render( 170 | hbs``, 174 | ); 175 | 176 | assert.dom("option:first-child").hasText("Choose this"); 177 | assert.dom("option:first-child").hasProperty("disabled", true); 178 | }); 179 | 180 | testDefault("prompt is selectable", async function (assert) { 181 | this.set("options", [ 182 | { key: 1, label: 1 }, 183 | { key: 2, label: 2 }, 184 | ]); 185 | 186 | await render( 187 | hbs``, 192 | ); 193 | 194 | assert.dom("option:first-child").hasProperty("disabled", false); 195 | }); 196 | 197 | testDefault("multiselect is working", async function (assert) { 198 | assert.expect(4); 199 | this.set("options", [ 200 | { id: 1, label: "label 1" }, 201 | { id: 2, label: "label 2" }, 202 | { id: 3, label: "label 3" }, 203 | ]); 204 | this.set("update", (values) => { 205 | assert.deepEqual(values, [ 206 | { id: 1, label: "label 1" }, 207 | { id: 3, label: "label 3" }, 208 | ]); 209 | }); 210 | 211 | await render( 212 | hbs``, 217 | ); 218 | 219 | await select("select", ["1", "3"]); 220 | assert.dom("option:first-child").hasProperty("selected", true); 221 | assert.dom("option:nth-child(2)").hasProperty("selected", false); 222 | assert.dom("option:last-child").hasProperty("selected", true); 223 | }); 224 | 225 | testDefault( 226 | "multiselect is working with plain options", 227 | async function (assert) { 228 | assert.expect(4); 229 | this.set("options", ["1", "2", "3"]); 230 | this.set("update", (values) => { 231 | assert.deepEqual(values, ["1", "3"]); 232 | }); 233 | 234 | await render( 235 | hbs``, 240 | ); 241 | 242 | await select("select", ["1", "3"]); 243 | assert.dom("option:first-child").hasProperty("selected", true); 244 | assert.dom("option:nth-child(2)").hasProperty("selected", false); 245 | assert.dom("option:last-child").hasProperty("selected", true); 246 | }, 247 | ); 248 | 249 | testDefault( 250 | "multiselect works with pre grouped options", 251 | async function (assert) { 252 | assert.expect(1); 253 | this.set("update", (values) => { 254 | assert.deepEqual(values, this.options[0].options); 255 | }); 256 | this.set("options", [ 257 | { 258 | groupName: "one", 259 | options: [ 260 | { id: 1, label: "First", type: "group1" }, 261 | { id: 2, label: "Second", type: "group1" }, 262 | ], 263 | }, 264 | { 265 | groupName: "two", 266 | options: [{ id: 3, label: "Third", type: "group2" }], 267 | }, 268 | ]); 269 | 270 | await render( 271 | hbs``, 278 | ); 279 | 280 | await select("select", ["1", "2"]); 281 | }, 282 | ); 283 | 284 | testDefault( 285 | "multiselect works with pre grouped options and optionsTargetPath", 286 | async function (assert) { 287 | assert.expect(1); 288 | this.set("update", (values) => { 289 | assert.deepEqual( 290 | values, 291 | this.options[0].options.map((val) => val.id), 292 | ); 293 | }); 294 | this.set("options", [ 295 | { 296 | groupName: "one", 297 | options: [ 298 | { id: 1, label: "First", type: "group1" }, 299 | { id: 2, label: "Second", type: "group1" }, 300 | ], 301 | }, 302 | { 303 | groupName: "two", 304 | options: [{ id: 3, label: "Third", type: "group2" }], 305 | }, 306 | ]); 307 | 308 | await render( 309 | hbs``, 317 | ); 318 | 319 | await select("select", ["1", "2"]); 320 | }, 321 | ); 322 | 323 | testUikit("it renders", async function (assert) { 324 | this.set("options", [ 325 | { 326 | key: "opt1", 327 | label: "Option 1", 328 | }, 329 | { 330 | key: "opt2", 331 | label: "Option 2", 332 | }, 333 | ]); 334 | 335 | await render( 336 | hbs``, 337 | ); 338 | 339 | assert.dom("select").hasClass("uk-select"); 340 | assert.dom("option").exists({ count: 2 }); 341 | }); 342 | 343 | testBootstrap("it renders", async function (assert) { 344 | this.set("options", [ 345 | { 346 | key: "opt1", 347 | label: "Option 1", 348 | }, 349 | { 350 | key: "opt2", 351 | label: "Option 2", 352 | }, 353 | ]); 354 | 355 | await render( 356 | hbs``, 357 | ); 358 | 359 | assert.dom("select").hasClass("form-control"); 360 | assert.dom("option").exists({ count: 2 }); 361 | }); 362 | 363 | testDefault( 364 | "prompt is selectable in compination with optionTargetPath, optionValuePath and optionLabelPath", 365 | async function (assert) { 366 | assert.expect(3); 367 | const values = [2, undefined]; 368 | this.set("options", [ 369 | { value: 1, text: "one" }, 370 | { value: 2, text: "two" }, 371 | ]); 372 | this.set("update", (value) => { 373 | assert.strictEqual(value, values.shift()); 374 | }); 375 | 376 | await render( 377 | hbs``, 386 | ); 387 | 388 | await select("select", "2"); 389 | await select("select", "option:first-child"); 390 | assert.dom("option:first-child").hasProperty("disabled", false); 391 | }, 392 | ); 393 | }, 394 | ); 395 | -------------------------------------------------------------------------------- /tests/integration/components/validated-input/types/textarea-test.js: -------------------------------------------------------------------------------- 1 | import { render } from "@ember/test-helpers"; 2 | import { setupRenderingTest } from "ember-qunit"; 3 | import hbs from "htmlbars-inline-precompile"; 4 | import { module } from "qunit"; 5 | 6 | import { 7 | testDefault, 8 | testUikit, 9 | testBootstrap, 10 | } from "dummy/tests/helpers/scenarios"; 11 | 12 | module( 13 | "Integration | Component | validated-input/types/textarea", 14 | function (hooks) { 15 | setupRenderingTest(hooks); 16 | 17 | testDefault("it renders", async function (assert) { 18 | await render(hbs``); 22 | 23 | assert.dom("textarea").exists(); 24 | }); 25 | 26 | testUikit("it renders", async function (assert) { 27 | await render(hbs``); 31 | 32 | assert.dom("textarea").hasClass("uk-textarea"); 33 | }); 34 | 35 | testBootstrap("it renders", async function (assert) { 36 | await render(hbs``); 40 | 41 | assert.dom("textarea").hasClass("form-control"); 42 | }); 43 | }, 44 | ); 45 | -------------------------------------------------------------------------------- /tests/integration/components/validated-label-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable ember/no-empty-glimmer-component-classes */ 2 | 3 | import { setComponentTemplate } from "@ember/component"; 4 | import { render } from "@ember/test-helpers"; 5 | import Component from "@glimmer/component"; 6 | import { setupRenderingTest } from "ember-qunit"; 7 | import hbs from "htmlbars-inline-precompile"; 8 | import { module } from "qunit"; 9 | 10 | import { testDefault } from "dummy/tests/helpers/scenarios"; 11 | 12 | module("Integration | Component | validated label", function (hooks) { 13 | setupRenderingTest(hooks); 14 | 15 | testDefault("it renders labels", async function (assert) { 16 | await render( 17 | hbs``, 18 | ); 19 | 20 | assert.dom("label").hasText("Default name"); 21 | const input = this.element.querySelector("input"); 22 | assert.dom("label").hasAttribute("for", input.getAttribute("id")); 23 | }); 24 | 25 | testDefault("it renders custom label component", async function (assert) { 26 | class CustomLabel extends Component {} 27 | setComponentTemplate( 28 | hbs``, 29 | CustomLabel, 30 | ); 31 | this.CustomLabel = CustomLabel; 32 | 33 | await render(hbs``); 34 | 35 | assert.dom("label").hasAttribute("style", "color: green;"); 36 | }); 37 | 38 | testDefault( 39 | "it passes original variables to custom component", 40 | async function (assert) { 41 | class CustomLabel extends Component {} 42 | setComponentTemplate( 43 | hbs``, 48 | CustomLabel, 49 | ); 50 | this.CustomLabel = CustomLabel; 51 | 52 | await render(hbs``); 58 | 59 | assert.dom("label").hasAttribute("style", "color: green;"); 60 | assert.dom("#orig-label").hasText("Name custom"); 61 | assert.dom("#orig-input-required").hasText("true"); 62 | 63 | const input = this.element.querySelector("input"); 64 | assert.dom("#orig-input-id").hasText(input.getAttribute("id")); 65 | }, 66 | ); 67 | 68 | testDefault( 69 | "it passes custom variables to custom component", 70 | async function (assert) { 71 | class CustomLabel extends Component {} 72 | setComponentTemplate( 73 | hbs``, 74 | CustomLabel, 75 | ); 76 | this.CustomLabel = CustomLabel; 77 | 78 | await render(hbs``); 84 | 85 | assert.dom("label").hasAttribute("style", "color: green;"); 86 | assert.dom("label").hasText("Awesome!"); 87 | }, 88 | ); 89 | }); 90 | -------------------------------------------------------------------------------- /tests/integration/helpers/class-list-test.js: -------------------------------------------------------------------------------- 1 | import { render } from "@ember/test-helpers"; 2 | import { hbs } from "ember-cli-htmlbars"; 3 | import { setupRenderingTest } from "ember-qunit"; 4 | import { module } from "qunit"; 5 | 6 | import { testDefault } from "dummy/tests/helpers/scenarios"; 7 | 8 | module("Integration | Helper | class-list", function (hooks) { 9 | setupRenderingTest(hooks); 10 | 11 | testDefault("it renders", async function (assert) { 12 | await render(hbs`{{class-list "foo" null undefined "bar" ""}}`); 13 | 14 | assert.dom(this.element).hasText("foo bar"); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import { setApplication } from "@ember/test-helpers"; 2 | import { start } from "ember-qunit"; 3 | import * as QUnit from "qunit"; 4 | import { setup } from "qunit-dom"; 5 | 6 | import Application from "dummy/app"; 7 | import config from "dummy/config/environment"; 8 | 9 | setApplication(Application.create(config.APP)); 10 | 11 | setup(QUnit.assert); 12 | 13 | start(); 14 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adfinis/ember-validated-form/4342564b7108d04900bffd0567780dcb5d72ed14/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tsconfig.declarations.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "declarationDir": "declarations", 5 | "emitDeclarationOnly": true, 6 | "noEmit": false, 7 | "rootDir": "." 8 | }, 9 | "include": ["addon", "addon-test-support"] 10 | } 11 | --------------------------------------------------------------------------------