├── tests ├── setup.ts ├── resources.ts ├── file-browser.spec.ts ├── toggle-switch.spec.ts ├── text-area.spec.ts ├── select-list.spec.ts └── input-box.spec.ts ├── .travis.yml ├── .gitignore ├── .npmignore ├── src ├── index.ts ├── templates │ ├── file-browser.html │ ├── text-area.html │ ├── toggle-switch.html │ ├── input-box.html │ └── select-list.html ├── file-browser.ts ├── toggle-switch.ts ├── vue-form-components.scss ├── text-area.ts ├── select-list.ts └── input-box.ts ├── tsconfig.json ├── webpack.test.config.js ├── webpack.config.js ├── package.json └── README.md /tests/setup.ts: -------------------------------------------------------------------------------- 1 | require('jsdom-global')(); 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | before_script: 5 | - npm install 6 | script: npm run test 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | index.html 3 | .idea 4 | dist 5 | npm-debug.log 6 | coverage 7 | .tmp 8 | .nyc_output 9 | yarn-error.log 10 | yarn.lock 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .babelrc 3 | webpack.config.js 4 | src 5 | .idea 6 | npm-debug.log 7 | tests 8 | jest 9 | coverage 10 | .tmp 11 | .nyc_output 12 | yarn-error.log 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /tests/resources.ts: -------------------------------------------------------------------------------- 1 | import { Component, Emit, Inject, Model, Prop, Provide, Vue, Watch } from 'vue-property-decorator' 2 | 3 | @Component({ 4 | template: `addon` 5 | }) 6 | export class StandardSlot extends Vue { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import InputBox from "./input-box.ts"; 2 | import SelectList from "./select-list.ts"; 3 | import TextArea from "./text-area.ts"; 4 | import ToggleSwitch from "./toggle-switch.ts"; 5 | import FileBrowser from './file-browser.ts'; 6 | 7 | exports.InputBox = InputBox; 8 | exports.SelectList = SelectList; 9 | exports.InputBlock = TextArea; 10 | exports.ToggleSwitch = ToggleSwitch; 11 | exports.FileBrowser = FileBrowser; 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "module": "es2015", 5 | "target": "es2015", 6 | "moduleResolution": "node", 7 | "removeComments": true, 8 | "isolatedModules": false, 9 | "experimentalDecorators": true, 10 | "emitDecoratorMetadata": true, 11 | "declaration": false, 12 | "noImplicitAny": false, 13 | "noImplicitUseStrict": false, 14 | "noLib": false, 15 | "preserveConstEnums": true, 16 | "suppressImplicitAnyIndexErrors": true, 17 | "pretty": true, 18 | "outDir": "build", 19 | "allowSyntheticDefaultImports": true, 20 | "lib": [ 21 | "dom", 22 | "es2015" 23 | ], 24 | }, 25 | "types": [ 26 | "node" 27 | ], 28 | "exclude": [ 29 | "node_modules/*", 30 | "tests" 31 | ], 32 | "files": [ 33 | "src/index.ts" 34 | ] 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/templates/file-browser.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 |
8 | 12 | 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /src/templates/text-area.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 |
8 | 9 | 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/templates/toggle-switch.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 |
8 | 15 | 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /webpack.test.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | module: { 3 | rules: [ 4 | { 5 | test: /\.tsx?$/, 6 | loader: 'ts-loader', 7 | exclude: /node_modules/, 8 | }, 9 | { 10 | test: /\.jsx?$/, 11 | loader: 'babel-loader', 12 | exclude: /node_modules/, 13 | options: { 14 | cacheDirectory: true, 15 | presets: [ 16 | ['env', { 17 | 'modules': false, 18 | 'targets': { 19 | 'browsers': ['> 2%'], 20 | uglify: true 21 | } 22 | }] 23 | ], 24 | plugins: ['transform-object-rest-spread'] 25 | } 26 | }, 27 | { 28 | test: /\.html$/, 29 | loader: 'html-loader', 30 | exclude: /node_modules/, 31 | }, 32 | { 33 | test: /\.vue$/, 34 | use: 'vue-loader' 35 | } 36 | ] 37 | }, 38 | 39 | resolve: { 40 | alias: { 41 | vue: 'vue/dist/vue.esm.js' 42 | } 43 | }, 44 | node: { 45 | fs: "empty" 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/file-browser.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Component from 'vue-class-component'; 3 | 4 | @Component({ 5 | template: require('./templates/file-browser.html'), 6 | props: { 7 | label: { 8 | type: String, 9 | required: false 10 | }, 11 | name: { 12 | type: String, 13 | required: true 14 | }, 15 | helper: { 16 | type: String, 17 | required: false 18 | }, 19 | required: { 20 | type: Boolean, 21 | default: false, 22 | required: false 23 | }, 24 | inline: { 25 | type: Boolean, 26 | default: false, 27 | required: false 28 | }, 29 | invalid: { 30 | type: Boolean, 31 | default: false, 32 | required: false 33 | }, 34 | errorMessage: { 35 | type: String, 36 | required: false, 37 | default: null 38 | }, 39 | metaUnderLabel: { 40 | type: Boolean, 41 | default: false, 42 | required: false 43 | } 44 | } 45 | }) 46 | export default class FileBrowser extends Vue { 47 | /** 48 | * Emit an input event up to the parent 49 | * @param {[type]} event 50 | */ 51 | public fileSelected(event: any): void { 52 | let fileList = event.target.files; 53 | this.$emit('file-selected', fileList[0]); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/toggle-switch.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Component from 'vue-class-component'; 3 | 4 | @Component({ 5 | template: require('./templates/toggle-switch.html'), 6 | model: { 7 | prop: 'checked', 8 | event: 'change' 9 | }, 10 | props: { 11 | label: { 12 | type: String, 13 | required: false 14 | }, 15 | labels: { 16 | type: Boolean, 17 | default: false 18 | }, 19 | name: { 20 | type: String, 21 | required: true 22 | }, 23 | helper: { 24 | type: String, 25 | required: false 26 | }, 27 | required: { 28 | type: Boolean, 29 | default: false, 30 | required: false 31 | }, 32 | inline: { 33 | type: Boolean, 34 | default: false, 35 | required: false 36 | }, 37 | invalid: { 38 | type: Boolean, 39 | default: false, 40 | required: false 41 | }, 42 | errorMessage: { 43 | type: String, 44 | required: false, 45 | default: null 46 | }, 47 | metaUnderLabel: { 48 | type: Boolean, 49 | default: false, 50 | required: false 51 | }, 52 | checked: { 53 | type: Boolean, 54 | default: false 55 | } 56 | } 57 | }) 58 | export default class ToggleSwitch extends Vue { 59 | /** 60 | * Emit a change event up to the parent 61 | * @param {[type]} value 62 | */ 63 | public updateValue(value): void { 64 | this.$emit('change', value); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/templates/input-box.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /src/templates/select-list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /src/vue-form-components.scss: -------------------------------------------------------------------------------- 1 | $slider-background: #ccc !default; 2 | $slider-primary: #428bca !default; 3 | 4 | /** 5 | * SWITCH COMPONENT STYLING 6 | */ 7 | .switch-component { 8 | position: relative; 9 | display: inline-block; 10 | width: 60px; 11 | height: 34px; 12 | } 13 | /* Hide default HTML checkbox */ 14 | .switch-component input {display:none;} 15 | /* The slider */ 16 | .slider { 17 | position: absolute; 18 | cursor: pointer; 19 | top: 0; 20 | left: 0; 21 | right: 0; 22 | bottom: 0; 23 | background-color: $slider-background; 24 | -webkit-transition: .4s; 25 | transition: .4s; 26 | } 27 | .slider:before { 28 | position: absolute; 29 | content: ""; 30 | height: 26px; 31 | width: 26px; 32 | left: 4px; 33 | bottom: 4px; 34 | background-color: white; 35 | -webkit-transition: .4s; 36 | transition: .4s; 37 | } 38 | input:checked + .slider { 39 | background-color: $slider-primary; 40 | } 41 | input:focus + .slider { 42 | box-shadow: 0 0 1px $slider-primary; 43 | } 44 | input:checked + .slider:before { 45 | -webkit-transform: translateX(26px); 46 | -ms-transform: translateX(26px); 47 | transform: translateX(26px); 48 | } 49 | /* Rounded sliders */ 50 | .slider.round { 51 | border-radius: 34px; 52 | display:flex; 53 | align-items: center; 54 | font-size: 11px; 55 | justify-content: space-between; 56 | } 57 | .slider.round:before { 58 | border-radius: 50%; 59 | } 60 | .slider.round .yes-label { 61 | padding-left: 5px; 62 | color: #FFF; 63 | text-transform: uppercase; 64 | } 65 | .slider.round .no-label { 66 | padding-right: 7px; 67 | text-transform: uppercase; 68 | } 69 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | let webpack = require('webpack'); 2 | let path = require('path'); 3 | let UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 4 | let nodeExternals = require('webpack-node-externals'); 5 | let ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | 7 | module.exports = { 8 | entry: ['./src/index.ts', './src/vue-form-components.scss'], 9 | 10 | output: { 11 | path: path.resolve(__dirname, 'dist/'), 12 | filename: 'build.js', 13 | publicPath: './dist' 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.tsx?$/, 20 | loader: 'ts-loader', 21 | exclude: /node_modules/, 22 | }, 23 | { 24 | test: /\.jsx?$/, 25 | loader: 'babel-loader', 26 | exclude: /node_modules/, 27 | options: { 28 | cacheDirectory: true, 29 | presets: [ 30 | ['env', { 31 | 'modules': false, 32 | 'targets': { 33 | 'browsers': ['> 2%'], 34 | uglify: true 35 | } 36 | }] 37 | ], 38 | plugins: ['transform-decorators-legacy', 'transform-object-rest-spread'] 39 | } 40 | }, 41 | { 42 | test: /\.html$/, 43 | loader: 'html-loader', 44 | exclude: /node_modules/, 45 | }, 46 | { 47 | test: /\.vue$/, 48 | use: 'vue-loader' 49 | }, 50 | { 51 | test: /\.(sass|scss)$/, 52 | loader: ExtractTextPlugin.extract(['css-loader', 'sass-loader']) 53 | } 54 | ] 55 | }, 56 | 57 | resolve: { 58 | alias: { 59 | vue: 'vue/dist/vue.js' 60 | } 61 | }, 62 | 63 | externals: [nodeExternals()], 64 | 65 | plugins: [ 66 | new ExtractTextPlugin({ // define where to save the file 67 | filename: '[name].css', 68 | allChunks: true, 69 | }), 70 | ] 71 | }; 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-form-components", 3 | "version": "2.2.0", 4 | "description": "A collection of bootstrap form components for Vue 2", 5 | "main": "dist/build.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/georgehanson/Vue-Form-Components.git" 9 | }, 10 | "scripts": { 11 | "build": "./node_modules/.bin/webpack", 12 | "test": "mocha-webpack --webpack-config=\"webpack.test.config.js\" --require=\"tests/setup.ts\" tests/**/*.spec.ts", 13 | "test:watch": "mocha-webpack --webpack-config=\"webpack.test.config.js\" --watch --require=\"tests/setup.ts\" tests/**/*.spec.ts" 14 | }, 15 | "keywords": [ 16 | "vue2", 17 | "vue", 18 | "form", 19 | "components", 20 | "bootstrap" 21 | ], 22 | "author": "George Hanson", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/georgehanson/Vue-Form-Components/issues" 26 | }, 27 | "homepage": "https://github.com/georgehanson/Vue-Form-Components#readme", 28 | "devDependencies": { 29 | "@types/node": "^8.0.45", 30 | "avoriaz": "^6.0.1", 31 | "babel-core": "^6.26.0", 32 | "babel-loader": "^7.1.2", 33 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 34 | "babel-preset-env": "^1.6.1", 35 | "babel-preset-es2015": "^6.24.1", 36 | "css-loader": "^0.28.7", 37 | "expect": "^21.2.1", 38 | "extract-text-webpack-plugin": "3.0.1", 39 | "html-loader": "^0.5.1", 40 | "jsdom": "^11.3.0", 41 | "jsdom-global": "^3.0.2", 42 | "mocha": "^4.0.1", 43 | "mocha-typescript": "1.1.11", 44 | "mocha-webpack": "^0.7.0", 45 | "node-sass": "^4.9.2", 46 | "sass-loader": "^6.0.6", 47 | "sinon": "^4.0.1", 48 | "ts-loader": "^3.0.3", 49 | "typescript": "2.5.3", 50 | "uglifyjs-webpack-plugin": "^1.0.0-beta.3", 51 | "vue": "^2.5.17", 52 | "vue-loader": "^13.3.0", 53 | "vue-property-decorator": "^6.0.0", 54 | "vue-template-compiler": "^2.5.17", 55 | "vue-test-utils": "^1.0.0-beta.2", 56 | "webpack": "^3.12.0", 57 | "webpack-node-externals": "^1.6.0", 58 | "webpack-notifier": "^1.5.0" 59 | }, 60 | "dependencies": { 61 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 62 | "vue-class-component": "^6.0.0" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/text-area.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Component from 'vue-class-component'; 3 | 4 | @Component({ 5 | template: require('./templates/text-area.html'), 6 | props: { 7 | label: { 8 | type: String, 9 | required: false 10 | }, 11 | placeholder: { 12 | type: String, 13 | required: false 14 | }, 15 | name: { 16 | type: String, 17 | required: true 18 | }, 19 | helper: { 20 | type: String, 21 | required: false 22 | }, 23 | required: { 24 | type: Boolean, 25 | default: false, 26 | required: false 27 | }, 28 | readonly: { 29 | type: Boolean, 30 | default: false, 31 | required: false 32 | }, 33 | small: { 34 | type: Boolean, 35 | default: false, 36 | required: false 37 | }, 38 | large: { 39 | type: Boolean, 40 | default: false, 41 | required: false 42 | }, 43 | inline: { 44 | type: Boolean, 45 | default: false, 46 | required: false 47 | }, 48 | invalid: { 49 | type: Boolean, 50 | default: false, 51 | required: false 52 | }, 53 | errorMessage: { 54 | type: String, 55 | required: false, 56 | default: null 57 | }, 58 | metaUnderLabel: { 59 | type: Boolean, 60 | default: false, 61 | required: false 62 | }, 63 | value: { 64 | type: String, 65 | default: null, 66 | required: false 67 | } 68 | } 69 | }) 70 | export default class TextArea extends Vue { 71 | /** 72 | * The classes for the input field 73 | * @return {string[]} 74 | */ 75 | get inputClasses(): any[] { 76 | let initialArray = ['form-control']; 77 | 78 | if (this.$props.large) { 79 | initialArray.push('form-control-lg'); 80 | } 81 | 82 | if (this.$props.small) { 83 | initialArray.push('form-control-sm'); 84 | } 85 | 86 | if (this.$props.plainText) { 87 | initialArray[0] += '-plaintext'; 88 | } 89 | 90 | if (this.$props.invalid) { 91 | initialArray.push('is-invalid'); 92 | } 93 | 94 | return initialArray; 95 | } 96 | 97 | /** 98 | * Emit an event that the enter key has been 99 | * pressed by the user 100 | */ 101 | public enterKeyPressed(): void { 102 | this.$emit('enter'); 103 | } 104 | 105 | /** 106 | * Emit an input event up to the parent 107 | * @param {[type]} value 108 | */ 109 | public updateValue(value): void { 110 | this.$emit('input', value); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/select-list.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Component from 'vue-class-component'; 3 | 4 | @Component({ 5 | template: require('./templates/select-list.html'), 6 | props: { 7 | label: { 8 | type: String, 9 | required: false 10 | }, 11 | placeholder: { 12 | type: String, 13 | required: false 14 | }, 15 | name: { 16 | type: String, 17 | required: true 18 | }, 19 | type: { 20 | type: String, 21 | default: 'text', 22 | required: false 23 | }, 24 | helper: { 25 | type: String, 26 | required: false 27 | }, 28 | required: { 29 | type: Boolean, 30 | default: false, 31 | required: false 32 | }, 33 | disabled: { 34 | type: Boolean, 35 | default: false, 36 | required: false 37 | }, 38 | small: { 39 | type: Boolean, 40 | default: false, 41 | required: false 42 | }, 43 | large: { 44 | type: Boolean, 45 | default: false, 46 | required: false 47 | }, 48 | inline: { 49 | type: Boolean, 50 | default: false, 51 | required: false 52 | }, 53 | invalid: { 54 | type: Boolean, 55 | default: false, 56 | required: false 57 | }, 58 | errorMessage: { 59 | type: String, 60 | required: false, 61 | default: null 62 | }, 63 | metaUnderLabel: { 64 | type: Boolean, 65 | default: false, 66 | required: false 67 | }, 68 | value: { 69 | type: [String, Number], 70 | required: false 71 | }, 72 | options: { 73 | type: Array, 74 | default: () => { return []; }, 75 | required: false 76 | }, 77 | keyName: { 78 | type: String, 79 | default: 'id' 80 | }, 81 | labelName: { 82 | type: String, 83 | default: 'label' 84 | } 85 | } 86 | }) 87 | export default class SelectList extends Vue { 88 | /** 89 | * The classes for the input field 90 | * @return {string[]} 91 | */ 92 | get inputClasses(): any[] { 93 | let initialArray = ['form-control']; 94 | 95 | if (this.$props.large && ! this.usingAddons) { 96 | initialArray.push('form-control-lg'); 97 | } 98 | 99 | if (this.$props.small && ! this.usingAddons) { 100 | initialArray.push('form-control-sm'); 101 | } 102 | 103 | if (this.$props.plainText) { 104 | initialArray[0] += '-plaintext'; 105 | } 106 | 107 | if (this.$props.invalid) { 108 | initialArray.push('is-invalid'); 109 | } 110 | 111 | return initialArray; 112 | } 113 | 114 | /** 115 | * Check if any add-ons are being used 116 | * 117 | * @return {boolean} 118 | */ 119 | get usingAddons(): boolean { 120 | return ! (Object.keys(this.$slots).length === 0 && this.$slots.constructor === Object) 121 | } 122 | 123 | /** 124 | * Check to see if a slot exists 125 | * @param {string} name [description] 126 | * @return {boolean} [description] 127 | */ 128 | public slotExists(name: string): boolean { 129 | return (name in this.$slots); 130 | } 131 | 132 | /** 133 | * Emit an input event up to the parent 134 | * @param {[type]} value 135 | */ 136 | public updateValue(value): void { 137 | this.$emit('input', value); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/input-box.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Component from 'vue-class-component'; 3 | 4 | @Component({ 5 | template: require('./templates/input-box.html'), 6 | props: { 7 | name: { 8 | type: String, 9 | required: true 10 | }, 11 | label: { 12 | type: String, 13 | required: false 14 | }, 15 | placeholder: { 16 | type: String, 17 | required: false 18 | }, 19 | type: { 20 | type: String, 21 | default: 'text', 22 | required: false 23 | }, 24 | helper: { 25 | type: String, 26 | required: false 27 | }, 28 | required: { 29 | type: Boolean, 30 | default: false, 31 | required: false 32 | }, 33 | readonly: { 34 | type: Boolean, 35 | default: false, 36 | required: false 37 | }, 38 | small: { 39 | type: Boolean, 40 | default: false, 41 | required: false 42 | }, 43 | large: { 44 | type: Boolean, 45 | default: false, 46 | required: false 47 | }, 48 | plainText: { 49 | type: Boolean, 50 | default: false, 51 | required: false 52 | }, 53 | inline: { 54 | type: Boolean, 55 | default: false, 56 | required: false 57 | }, 58 | invalid: { 59 | type: Boolean, 60 | default: false, 61 | required: false 62 | }, 63 | errorMessage: { 64 | type: String, 65 | required: false, 66 | default: null 67 | }, 68 | metaUnderLabel: { 69 | type: Boolean, 70 | default: false, 71 | required: false 72 | }, 73 | value: { 74 | type: String, 75 | default: null, 76 | required: false 77 | }, 78 | maxLength: { 79 | type: Number, 80 | default() { 81 | return 524288; 82 | }, 83 | required: false 84 | }, 85 | autoComplete: { 86 | type: Boolean, 87 | default: true, 88 | required: false 89 | } 90 | } 91 | }) 92 | export default class TextArea extends Vue { 93 | /** 94 | * The classes for the input field 95 | * @return {string[]} 96 | */ 97 | get inputClasses(): any[] { 98 | let initialArray = ['form-control']; 99 | 100 | if (this.$props.large && ! this.usingAddons) { 101 | initialArray.push('form-control-lg'); 102 | } 103 | 104 | if (this.$props.small && ! this.usingAddons) { 105 | initialArray.push('form-control-sm'); 106 | } 107 | 108 | if (this.$props.plainText) { 109 | initialArray[0] += '-plaintext'; 110 | } 111 | 112 | if (this.$props.invalid) { 113 | initialArray.push('is-invalid'); 114 | } 115 | 116 | return initialArray; 117 | } 118 | 119 | /** 120 | * Check if any add-ons are being used 121 | * 122 | * @return {boolean} 123 | */ 124 | get usingAddons(): boolean { 125 | return ! (Object.keys(this.$slots).length === 0 && this.$slots.constructor === Object) 126 | } 127 | 128 | /** 129 | * Check to see if a slot exists 130 | * @param {string} name [description] 131 | * @return {boolean} [description] 132 | */ 133 | public slotExists(name: string): boolean { 134 | return (name in this.$slots); 135 | } 136 | 137 | /** 138 | * Emit an event that the enter key has been 139 | * pressed by the user 140 | */ 141 | public enterKeyPressed(): void { 142 | this.$emit('enter'); 143 | } 144 | 145 | /** 146 | * Emit an input event up to the parent 147 | * @param {[type]} value 148 | */ 149 | public updateValue(value): void { 150 | this.$emit('input', value); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /tests/file-browser.spec.ts: -------------------------------------------------------------------------------- 1 | import { suite, test, slow, timeout } from "mocha-typescript"; 2 | import {mount} from "avoriaz"; 3 | import FileBrowser from "./../src/file-browser.ts"; 4 | import expect from 'expect'; 5 | import {StandardSlot} from "./resources.ts"; 6 | import sinon from 'sinon'; 7 | 8 | suite("FileBrowser", () => { 9 | test("it can set the name", () => { 10 | let wrapper: any = mount(FileBrowser, { 11 | propsData: { 12 | name: 'active' 13 | } 14 | }); 15 | 16 | expect(wrapper.first('input').hasAttribute('name')).toBe(true); 17 | expect(wrapper.first('input').getAttribute('name')).toBe('active'); 18 | }); 19 | 20 | test("the label gets output", () => { 21 | let wrapper: any = mount(FileBrowser, { 22 | propsData: { 23 | name: 'active', 24 | label: 'My Input' 25 | } 26 | }); 27 | 28 | expect(wrapper.first('label').text()).toBe('My Input'); 29 | }); 30 | 31 | test("if no label is specified it does not show it", () => { 32 | let wrapper: any = mount(FileBrowser, { 33 | propsData: { 34 | name: 'active' 35 | } 36 | }); 37 | expect(wrapper.find('.col-form-label').length).toBe(0); 38 | }); 39 | 40 | test("if no helper is provided it does not show one", () => { 41 | let wrapper: any = mount(FileBrowser, { 42 | propsData: { 43 | name: 'active', 44 | } 45 | }); 46 | expect(wrapper.find('small.form-text').length).toBe(0); 47 | expect(wrapper.first('.form-group').hasClass('has-helper')).toBe(false); 48 | }); 49 | 50 | test("if helper text is provided it does show it", () => { 51 | let wrapper: any = mount(FileBrowser, { 52 | propsData: { 53 | name: 'active', 54 | helper: 'Please select a active' 55 | } 56 | }); 57 | 58 | expect(wrapper.find('small.form-text').length).toBe(1); 59 | expect(wrapper.first('small.form-text').text()).toBe("Please select a active"); 60 | expect(wrapper.first('.form-group').hasClass('has-helper')).toBe(true); 61 | }); 62 | 63 | test("if the field is not required it does not have the required attributes", () => { 64 | let wrapper: any = mount(FileBrowser, { 65 | propsData: { 66 | name: 'active', 67 | } 68 | }); 69 | 70 | expect(wrapper.find('span.required').length).toBe(0); 71 | }); 72 | 73 | test("if the field is required it have the required attributes", () => { 74 | let wrapper: any = mount(FileBrowser, { 75 | propsData: { 76 | name: 'active', 77 | label: 'Your Email', 78 | required: true 79 | } 80 | }); 81 | 82 | expect(wrapper.find('span.required').length).toBe(1); 83 | expect(wrapper.first('span.required').text()).toBe('*'); 84 | }); 85 | 86 | test("the input can be inline", () => { 87 | let wrapper: any = mount(FileBrowser, { 88 | propsData: { 89 | name: 'active', 90 | label: 'My Label', 91 | inline: true 92 | } 93 | }); 94 | 95 | expect(wrapper.find('.col-sm-4').length).toBe(1); 96 | expect(wrapper.first('.form-group').hasClass('row')).toBe(true); 97 | expect(wrapper.first('.col-sm-4').contains('label')).toBe(true); 98 | expect(wrapper.first('label').hasClass('col-form-label')).toBe(true); 99 | expect(wrapper.find('.col-sm-8').length).toBe(1); 100 | }); 101 | 102 | test("if the input is not inline, it does not have the row class", () => { 103 | let wrapper: any = mount(FileBrowser, { 104 | propsData: { 105 | name: 'active', 106 | label: 'My Label', 107 | } 108 | }); 109 | 110 | expect(wrapper.first('.form-group').hasClass('row')).toBe(false); 111 | }); 112 | 113 | test("the field can be marked as invalid", () => { 114 | let wrapper: any = mount(FileBrowser, { 115 | propsData: { 116 | name: 'active', 117 | label: 'My Label', 118 | invalid: true, 119 | errorMessage: "There was an error" 120 | } 121 | }); 122 | 123 | expect(wrapper.first('.form-group').contains('.invalid-feedback')).toBe(true); 124 | expect(wrapper.first('.invalid-feedback').text()).toBe("There was an error"); 125 | }); 126 | 127 | test("the field does not show invalid feedback if it has not been marked as invalid", () => { 128 | let wrapper: any = mount(FileBrowser, { 129 | propsData: { 130 | name: 'active', 131 | label: 'My Label', 132 | errorMessage: "There was an error" 133 | } 134 | }); 135 | 136 | expect(wrapper.first('.form-group').contains('.invalid-feedback')).toBe(false); 137 | }); 138 | 139 | test("the field does not show invalid feedback if no active has been passed", () => { 140 | let wrapper: any = mount(FileBrowser, { 141 | propsData: { 142 | name: 'active', 143 | label: 'My Label', 144 | invalid: true 145 | } 146 | }); 147 | 148 | expect(wrapper.first('.invalid-feedback').contains('.invalid-feedback')).toBe(false); 149 | }); 150 | 151 | test("the helper text can be placed under the label", () => { 152 | let wrapper: any = mount(FileBrowser, { 153 | propsData: { 154 | name: 'active', 155 | label: 'My Label', 156 | helper: 'This is helper text', 157 | inline: true, 158 | metaUnderLabel: true 159 | } 160 | }); 161 | 162 | expect(wrapper.first('.col-sm-4').contains('.form-text')).toBe(true); 163 | expect(wrapper.first('.col-sm-8').contains('.form-text')).toBe(false); 164 | }); 165 | 166 | test("the error text can be placed under the label", () => { 167 | let wrapper: any = mount(FileBrowser, { 168 | propsData: { 169 | name: 'active', 170 | label: 'My Label', 171 | helper: 'This is helper text', 172 | inline: true, 173 | metaUnderLabel: true, 174 | invalid: true, 175 | erroractive: 'There was an error' 176 | } 177 | }); 178 | 179 | expect(wrapper.first('.col-sm-4').contains('.invalid-feedback')).toBe(true); 180 | expect(wrapper.first('.col-sm-8').contains('.invalid-feedback')).toBe(false); 181 | }); 182 | 183 | test("it emits an event on change", () => { 184 | let wrapper: any = mount(FileBrowser, { 185 | propsData: { 186 | name: 'active', 187 | label: 'My Label' 188 | } 189 | }); 190 | const spy = sinon.spy(wrapper.vm, '$emit'); 191 | wrapper.first('input').trigger("change"); 192 | expect(spy.args[0][0]).toBe('file-selected') 193 | }); 194 | }); 195 | -------------------------------------------------------------------------------- /tests/toggle-switch.spec.ts: -------------------------------------------------------------------------------- 1 | import { suite, test, slow, timeout } from "mocha-typescript"; 2 | import {mount} from "avoriaz"; 3 | import ToggleSwitch from "./../src/toggle-switch.ts"; 4 | import expect from 'expect'; 5 | import {StandardSlot} from "./resources.ts"; 6 | import sinon from 'sinon'; 7 | 8 | suite("ToggleSwitch", () => { 9 | test("it can set the name", () => { 10 | let wrapper: any = mount(ToggleSwitch, { 11 | propsData: { 12 | name: 'active' 13 | } 14 | }); 15 | 16 | expect(wrapper.first('input').hasAttribute('name')).toBe(true); 17 | expect(wrapper.first('input').getAttribute('name')).toBe('active'); 18 | }); 19 | 20 | test("the label gets output", () => { 21 | let wrapper: any = mount(ToggleSwitch, { 22 | propsData: { 23 | name: 'active', 24 | label: 'My Input' 25 | } 26 | }); 27 | 28 | expect(wrapper.first('label').text()).toBe('My Input'); 29 | }); 30 | 31 | test("if no label is specified it does not show it", () => { 32 | let wrapper: any = mount(ToggleSwitch, { 33 | propsData: { 34 | name: 'active' 35 | } 36 | }); 37 | expect(wrapper.find('.col-form-label').length).toBe(0); 38 | }); 39 | 40 | test("if no helper is provided it does not show one", () => { 41 | let wrapper: any = mount(ToggleSwitch, { 42 | propsData: { 43 | name: 'active', 44 | } 45 | }); 46 | expect(wrapper.find('small.form-text').length).toBe(0); 47 | expect(wrapper.first('.form-group').hasClass('has-helper')).toBe(false); 48 | }); 49 | 50 | test("if helper text is provided it does show it", () => { 51 | let wrapper: any = mount(ToggleSwitch, { 52 | propsData: { 53 | name: 'active', 54 | helper: 'Please select a active' 55 | } 56 | }); 57 | 58 | expect(wrapper.find('small.form-text').length).toBe(1); 59 | expect(wrapper.first('small.form-text').text()).toBe("Please select a active"); 60 | expect(wrapper.first('.form-group').hasClass('has-helper')).toBe(true); 61 | }); 62 | 63 | test("if the field is not required it does not have the required attributes", () => { 64 | let wrapper: any = mount(ToggleSwitch, { 65 | propsData: { 66 | name: 'active', 67 | } 68 | }); 69 | 70 | expect(wrapper.find('span.required').length).toBe(0); 71 | }); 72 | 73 | test("if the field is required it have the required attributes", () => { 74 | let wrapper: any = mount(ToggleSwitch, { 75 | propsData: { 76 | name: 'active', 77 | label: 'Your Email', 78 | required: true 79 | } 80 | }); 81 | 82 | expect(wrapper.find('span.required').length).toBe(1); 83 | expect(wrapper.first('span.required').text()).toBe('*'); 84 | }); 85 | 86 | test("the input can be inline", () => { 87 | let wrapper: any = mount(ToggleSwitch, { 88 | propsData: { 89 | name: 'active', 90 | label: 'My Label', 91 | inline: true 92 | } 93 | }); 94 | 95 | expect(wrapper.find('.col-sm-4').length).toBe(1); 96 | expect(wrapper.first('.form-group').hasClass('row')).toBe(true); 97 | expect(wrapper.first('.col-sm-4').contains('label')).toBe(true); 98 | expect(wrapper.first('label').hasClass('col-form-label')).toBe(true); 99 | expect(wrapper.find('.col-sm-8').length).toBe(1); 100 | }); 101 | 102 | test("if the input is not inline, it does not have the row class", () => { 103 | let wrapper: any = mount(ToggleSwitch, { 104 | propsData: { 105 | name: 'active', 106 | label: 'My Label', 107 | } 108 | }); 109 | 110 | expect(wrapper.first('.form-group').hasClass('row')).toBe(false); 111 | }); 112 | 113 | test("the field can be marked as invalid", () => { 114 | let wrapper: any = mount(ToggleSwitch, { 115 | propsData: { 116 | name: 'active', 117 | label: 'My Label', 118 | invalid: true, 119 | errorMessage: "There was an error" 120 | } 121 | }); 122 | 123 | expect(wrapper.first('.form-group').contains('.invalid-feedback')).toBe(true); 124 | expect(wrapper.first('.invalid-feedback').text()).toBe("There was an error"); 125 | }); 126 | 127 | test("the field does not show invalid feedback if it has not been marked as invalid", () => { 128 | let wrapper: any = mount(ToggleSwitch, { 129 | propsData: { 130 | name: 'active', 131 | label: 'My Label', 132 | errorMessage: "There was an error" 133 | } 134 | }); 135 | 136 | expect(wrapper.first('.form-group').contains('.invalid-feedback')).toBe(false); 137 | }); 138 | 139 | test("the field does not show invalid feedback if no active has been passed", () => { 140 | let wrapper: any = mount(ToggleSwitch, { 141 | propsData: { 142 | name: 'active', 143 | label: 'My Label', 144 | invalid: true 145 | } 146 | }); 147 | 148 | expect(wrapper.first('.invalid-feedback').contains('.invalid-feedback')).toBe(false); 149 | }); 150 | 151 | test("the helper text can be placed under the label", () => { 152 | let wrapper: any = mount(ToggleSwitch, { 153 | propsData: { 154 | name: 'active', 155 | label: 'My Label', 156 | helper: 'This is helper text', 157 | inline: true, 158 | metaUnderLabel: true 159 | } 160 | }); 161 | 162 | expect(wrapper.first('.col-sm-4').contains('.form-text')).toBe(true); 163 | expect(wrapper.first('.col-sm-8').contains('.form-text')).toBe(false); 164 | }); 165 | 166 | test("the error text can be placed under the label", () => { 167 | let wrapper: any = mount(ToggleSwitch, { 168 | propsData: { 169 | name: 'active', 170 | label: 'My Label', 171 | helper: 'This is helper text', 172 | inline: true, 173 | metaUnderLabel: true, 174 | invalid: true, 175 | erroractive: 'There was an error' 176 | } 177 | }); 178 | 179 | expect(wrapper.first('.col-sm-4').contains('.invalid-feedback')).toBe(true); 180 | expect(wrapper.first('.col-sm-8').contains('.invalid-feedback')).toBe(false); 181 | }); 182 | 183 | test("it emits an event on change", () => { 184 | let wrapper: any = mount(ToggleSwitch, { 185 | propsData: { 186 | name: 'active', 187 | label: 'My Label' 188 | } 189 | }); 190 | const spy = sinon.spy(wrapper.vm, '$emit'); 191 | wrapper.first('input').trigger("change"); 192 | expect(spy.args[0][0]).toBe('change') 193 | }); 194 | }); 195 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue Form Components 2 | 3 | [![Build Status](https://travis-ci.org/georgehanson/Vue-Form-Components.svg?branch=master)](https://travis-ci.org/georgehanson/Vue-Form-Components) 4 | [![npm version](https://badge.fury.io/js/vue-form-components.svg)](https://badge.fury.io/js/vue-form-components) 5 | [![npm](https://img.shields.io/npm/dt/vue-form-components.svg)](https://www.npmjs.com/package/vue-form-components) 6 | 7 | This package provides easy form wrappers for Vue2, based upon the [Bootstrap v4](https://getbootstrap.com/) CSS Framework. 8 | 9 | There are a variety of components that this package provides. These are: 10 | 11 | - Standard Input Box 12 | - Text Area 13 | - Select List 14 | - Toggle Switch 15 | - File Browser 16 | 17 | ## Installation 18 | 19 | You can install this package by running the following command: `npm install --save vue-form-components` or `yarn add vue-form-components` 20 | 21 | You can then import this package into your project. 22 | 23 | `Import VueFormComponents from 'vue-form-components'` 24 | 25 | ## Usage 26 | 27 | I recommend when using this package to declare the components as global components. This can be done one of the following ways: 28 | 29 | ```javascript 30 | Import VueFormComponents from 'vue-form-components'; 31 | Vue.component('input-box', VueFormComponents.InputBox); 32 | ``` 33 | 34 | ### Standard Input Box 35 | 36 | This component is designed to save time and repetition by not having to duplicate form groups. The name for this component is `InputBox`. This component can be used in conjunction with the `v-model` directive. Below is an example of how you might use the component: 37 | 38 | ```html 39 | 40 | ``` 41 | 42 | #### Addons 43 | With the Standard input box component, you can also use input group addons. This can be done by simply adding slots. 44 | 45 | | Slot | Description | 46 | | ------------ | ----------- | 47 | | leftAddon | A standard input group addon on the left side of the input field | 48 | | rightAddon | A standard input group addon on the right side of the input field | 49 | | leftButton | An input group button on the left side of the input field | 50 | | rightButton | An input group button on the right side of the input | 51 | 52 | For example, if I wanted to create an input box, with an addon on the right hand side, I could do the following: 53 | 54 | ```html 55 | 56 |
@example.com
57 |
58 | ``` 59 | 60 | Alternatively, for a button on the right hand side, I could do the following: 61 | 62 | ```html 63 | 64 | 65 | 66 | ``` 67 | 68 | | Prop | Type | Default | Required | Description | 69 | | ------------ | ------- | ---------- | --------- | ----------- | 70 | | name | String | | Yes | The input name for the field | 71 | | label | String | | No | The label for the input | 72 | | helper | String | | No | Helper text | 73 | | invalid | Boolean | false | No | Whether or not to show a validation error | 74 | | errorMessage | String | | No | The error message to show | 75 | | placeholder | String | | No | A placeholder for the input | 76 | | inline | Boolean | false | No | Show the label next to the input | 77 | | type | String | text | No | The HTML input type | 78 | | required | Boolean | false | No | Mark the field as required | 79 | | readonly | Boolean | false | No | Mark the field as readonly | 80 | | small | Boolean | false | No | Show a small input | 81 | | large | Boolean | false | No | Show a large input | 82 | | plainText | Boolean | false | No | Show a plain text input | 83 | | metaUnderLabel | Boolean | false | No | Show the meta details under the label instead of the input | 84 | 85 | ### Toggle Switch 86 | 87 | This component displays a nice alternative to a standard checkbox. The name for this component is `ToggleSwitch`. 88 | This component can be used in conjunction with the `v-model` directive. 89 | 90 | | Prop | Type | Default | Required | Description | 91 | | ------------ | ------- | ---------- | --------- | ----------- | 92 | | name | String | | Yes | The checkbox name | 93 | | label | String | | No | The label for the checkbox | 94 | | labels | Boolean | false | No | Show yes / no labels on the switch | 95 | | helper | String | | No | Helper text | 96 | | id | String | | No | The id for the checkbox | 97 | | stacked | Boolean | false | No | Show the label above the switch component 98 | | labelColumn | String | col-sm-2 | No | The bootstrap column for the label | 99 | | inputColumn | String | col-sm-10 | No | The bootstrap column for the input | 100 | 101 | ### Text Area 102 | 103 | This component generates a textarea within a bootstrap form group. The name for this component is `TextArea`. 104 | This component can be used in conjunction with the `v-model` directive. 105 | 106 | | Prop | Type | Default | Required | Description | 107 | | ------------ | ------- | ---------- | --------- | ----------- | 108 | | name | String | | Yes | The textarea name | 109 | | label | String | | No | The label for the textarea | 110 | | helper | String | | No | Helper text | 111 | | showError | Boolean | false | No | Whether or not to show a validation error | 112 | | errorMessage | String | | No | The error message to show | 113 | | stacked | Boolean | false | No | Show the label above the textarea | 114 | | required | Boolean | false | No | Mark the field as required | 115 | | id | String | | No | The id for the html input | 116 | 117 | ### Select List 118 | 119 | This component generates a select input within a bootstrap form group. The name for this component is `SelectList`. 120 | This component can be used in conjunction with the `v-model` directive. 121 | 122 | #### Addons 123 | With the Select List component, you can also use input group addons. This can be done by simply adding slots. 124 | 125 | | Slot | Description | 126 | | ------------ | ----------- | 127 | | leftAddon | An addon on the left side of the input field | 128 | | rightAddon | An addon on the right side of the input field | 129 | | leftBtn | An input group button on the left side of the input field | 130 | | rightBtn | An input group button on the right side of the input | 131 | 132 | | Prop | Type | Default | Required | Description | 133 | | ------------ | ------- | ---------- | --------- | ----------- | 134 | | name | String | | Yes | The select list name | 135 | | label | String | | No | The label for the select list | 136 | | options | Array | | Yes | The options to choose from | 137 | | keyName | String | id | No | The name of the key for the value in the options | 138 | | labelName | String | label | No | The name of the key for the label in the options | 139 | | helper | String | | No | Helper text | 140 | | showError | Boolean | false | No | Whether or not to show a validation error | 141 | | errorMessage | String | | No | The error message to show | 142 | | stacked | Boolean | false | No | Show the label above the textarea | 143 | | required | Boolean | false | No | Mark the field as required | 144 | 145 | 146 | More docs coming soon 147 | -------------------------------------------------------------------------------- /tests/text-area.spec.ts: -------------------------------------------------------------------------------- 1 | import { suite, test, slow, timeout } from "mocha-typescript"; 2 | import {mount} from "avoriaz"; 3 | import TextArea from "./../src/text-area.ts"; 4 | import expect from 'expect'; 5 | import {StandardSlot} from "./resources.ts"; 6 | import sinon from 'sinon'; 7 | 8 | suite("TextArea", () => { 9 | test("it can set the name", () => { 10 | let wrapper: any = mount(TextArea, { 11 | propsData: { 12 | name: 'message' 13 | } 14 | }); 15 | 16 | expect(wrapper.first('textarea').hasAttribute('name')).toBe(true); 17 | expect(wrapper.first('textarea').getAttribute('name')).toBe('message'); 18 | }); 19 | 20 | test("the label gets output", () => { 21 | let wrapper: any = mount(TextArea, { 22 | propsData: { 23 | name: 'message', 24 | label: 'My Input' 25 | } 26 | }); 27 | 28 | expect(wrapper.first('label').text()).toBe('My Input'); 29 | }); 30 | 31 | test("if no label is specified it does not show it", () => { 32 | let wrapper: any = mount(TextArea, { 33 | propsData: { 34 | name: 'message' 35 | } 36 | }); 37 | expect(wrapper.find('label').length).toBe(0); 38 | }); 39 | 40 | test("if no placeholder is specified it does not show one", () => { 41 | let wrapper: any = mount(TextArea, { 42 | propsData: { 43 | name: 'message' 44 | } 45 | }); 46 | expect(wrapper.find('option').length).toBe(0); 47 | }); 48 | 49 | test("if a placeholder is specified it shows one", () => { 50 | let wrapper: any = mount(TextArea, { 51 | propsData: { 52 | name: 'message', 53 | placeholder: 'My Placeholder' 54 | } 55 | }); 56 | 57 | expect(wrapper.first('textarea').hasAttribute('placeholder')).toBe(true); 58 | expect(wrapper.first('textarea').getAttribute('placeholder')).toBe('My Placeholder'); 59 | }); 60 | 61 | test("if no helper is provided it does not show one", () => { 62 | let wrapper: any = mount(TextArea, { 63 | propsData: { 64 | name: 'message', 65 | } 66 | }); 67 | expect(wrapper.find('small.form-text').length).toBe(0); 68 | expect(wrapper.first('.form-group').hasClass('has-helper')).toBe(false); 69 | }); 70 | 71 | test("if helper text is provided it does show it", () => { 72 | let wrapper: any = mount(TextArea, { 73 | propsData: { 74 | name: 'message', 75 | helper: 'Please select a message' 76 | } 77 | }); 78 | 79 | expect(wrapper.find('small.form-text').length).toBe(1); 80 | expect(wrapper.first('small.form-text').text()).toBe("Please select a message"); 81 | expect(wrapper.first('.form-group').hasClass('has-helper')).toBe(true); 82 | }); 83 | 84 | test("if the field is not required it does not have the required attributes", () => { 85 | let wrapper: any = mount(TextArea, { 86 | propsData: { 87 | name: 'message', 88 | } 89 | }); 90 | 91 | expect(wrapper.find('span.required').length).toBe(0); 92 | expect(wrapper.first('textarea').hasAttribute('required')).toBe(false); 93 | }); 94 | 95 | test("if the field is required it have the required attributes", () => { 96 | let wrapper: any = mount(TextArea, { 97 | propsData: { 98 | name: 'message', 99 | label: 'Your Email', 100 | required: true 101 | } 102 | }); 103 | 104 | expect(wrapper.find('span.required').length).toBe(1); 105 | expect(wrapper.first('span.required').text()).toBe('*'); 106 | expect(wrapper.first('textarea').hasAttribute('required')).toBe(true); 107 | }); 108 | 109 | test("if the input is required but no label has been specified the input field has the required attribute", () => { 110 | let wrapper: any = mount(TextArea, { 111 | propsData: { 112 | name: 'message', 113 | required: true 114 | } 115 | }); 116 | 117 | expect(wrapper.first('textarea').hasAttribute('required')).toBe(true); 118 | }); 119 | 120 | test("the input can have the readonly attribute", () => { 121 | let wrapper: any = mount(TextArea, { 122 | propsData: { 123 | name: 'message', 124 | readonly: true, 125 | } 126 | }); 127 | 128 | expect(wrapper.first('textarea').hasAttribute('readonly')).toBe(true); 129 | }); 130 | 131 | test("by default the input does not have a readonly attribute", () => { 132 | let wrapper: any = mount(TextArea, { 133 | propsData: { 134 | name: 'message' 135 | } 136 | }); 137 | 138 | expect(wrapper.first('textarea').hasAttribute('readonly')).toBe(false); 139 | }); 140 | 141 | test("by default the class for the input is form-control", () => { 142 | let wrapper: any = mount(TextArea, { 143 | propsData: { 144 | name: 'message' 145 | } 146 | }); 147 | 148 | expect(wrapper.first('textarea').hasClass('form-control')).toBe(true); 149 | }); 150 | 151 | test("the form-control can be large", () => { 152 | let wrapper: any = mount(TextArea, { 153 | propsData: { 154 | name: 'message', 155 | large: true 156 | } 157 | }); 158 | 159 | expect(wrapper.first('textarea').hasClass('form-control')).toBe(true); 160 | expect(wrapper.first('textarea').hasClass('form-control-lg')).toBe(true); 161 | }); 162 | 163 | test("the form-control can be small", () => { 164 | let wrapper: any = mount(TextArea, { 165 | propsData: { 166 | name: 'message', 167 | small: true 168 | } 169 | }); 170 | 171 | expect(wrapper.first('textarea').hasClass('form-control')).toBe(true); 172 | expect(wrapper.first('textarea').hasClass('form-control-sm')).toBe(true); 173 | }); 174 | 175 | test("the input can be inline", () => { 176 | let wrapper: any = mount(TextArea, { 177 | propsData: { 178 | name: 'message', 179 | label: 'My Label', 180 | inline: true 181 | } 182 | }); 183 | 184 | expect(wrapper.find('.col-sm-4').length).toBe(1); 185 | expect(wrapper.first('.form-group').hasClass('row')).toBe(true); 186 | expect(wrapper.first('.col-sm-4').contains('label')).toBe(true); 187 | expect(wrapper.first('label').hasClass('col-form-label')).toBe(true); 188 | expect(wrapper.find('.col-sm-8').length).toBe(1); 189 | }); 190 | 191 | test("if the input is not inline, it does not have the row class", () => { 192 | let wrapper: any = mount(TextArea, { 193 | propsData: { 194 | name: 'message', 195 | label: 'My Label', 196 | } 197 | }); 198 | 199 | expect(wrapper.first('.form-group').hasClass('row')).toBe(false); 200 | }); 201 | 202 | test("the field can be marked as invalid", () => { 203 | let wrapper: any = mount(TextArea, { 204 | propsData: { 205 | name: 'message', 206 | label: 'My Label', 207 | invalid: true, 208 | errorMessage: "There was an error" 209 | } 210 | }); 211 | 212 | expect(wrapper.first('.form-control').hasClass('is-invalid')).toBe(true); 213 | expect(wrapper.first('.form-group').contains('.invalid-feedback')).toBe(true); 214 | expect(wrapper.first('.invalid-feedback').text()).toBe("There was an error"); 215 | }); 216 | 217 | test("the field does not show invalid feedback if it has not been marked as invalid", () => { 218 | let wrapper: any = mount(TextArea, { 219 | propsData: { 220 | name: 'message', 221 | label: 'My Label', 222 | errorMessage: "There was an error" 223 | } 224 | }); 225 | 226 | expect(wrapper.first('.form-control').hasClass('is-invalid')).toBe(false); 227 | expect(wrapper.first('.form-group').contains('.invalid-feedback')).toBe(false); 228 | }); 229 | 230 | test("the field does not show invalid feedback if no message has been passed", () => { 231 | let wrapper: any = mount(TextArea, { 232 | propsData: { 233 | name: 'message', 234 | label: 'My Label', 235 | invalid: true 236 | } 237 | }); 238 | 239 | expect(wrapper.first('.form-control').hasClass('is-invalid')).toBe(true); 240 | expect(wrapper.first('.invalid-feedback').contains('.invalid-feedback')).toBe(false); 241 | }); 242 | 243 | test("the helper text can be placed under the label", () => { 244 | let wrapper: any = mount(TextArea, { 245 | propsData: { 246 | name: 'message', 247 | label: 'My Label', 248 | helper: 'This is helper text', 249 | inline: true, 250 | metaUnderLabel: true 251 | } 252 | }); 253 | 254 | expect(wrapper.first('.col-sm-4').contains('.form-text')).toBe(true); 255 | expect(wrapper.first('.col-sm-8').contains('.form-text')).toBe(false); 256 | }); 257 | 258 | test("the error text can be placed under the label", () => { 259 | let wrapper: any = mount(TextArea, { 260 | propsData: { 261 | name: 'message', 262 | label: 'My Label', 263 | helper: 'This is helper text', 264 | inline: true, 265 | metaUnderLabel: true, 266 | invalid: true, 267 | errorMessage: 'There was an error' 268 | } 269 | }); 270 | 271 | expect(wrapper.first('.col-sm-4').contains('.invalid-feedback')).toBe(true); 272 | expect(wrapper.first('.col-sm-8').contains('.invalid-feedback')).toBe(false); 273 | }); 274 | 275 | test("it emits an event on input", () => { 276 | let wrapper: any = mount(TextArea, { 277 | propsData: { 278 | name: 'message', 279 | label: 'My Label' 280 | } 281 | }); 282 | const spy = sinon.spy(wrapper.vm, '$emit'); 283 | wrapper.first('.form-control').trigger("input"); 284 | expect(spy.args[0][0]).toBe('input') 285 | }); 286 | 287 | test("it emits an event on enter key", () => { 288 | let wrapper: any = mount(TextArea, { 289 | propsData: { 290 | name: 'message', 291 | label: 'My Label' 292 | } 293 | }); 294 | const spy = sinon.spy(wrapper.vm, '$emit'); 295 | wrapper.first('.form-control').trigger("keyup.enter"); 296 | expect(spy.args[0][0]).toBe('enter') 297 | }); 298 | 299 | test("the value of the textarea can be set", () => { 300 | let wrapper: any = mount(TextArea, { 301 | propsData: { 302 | name: 'message', 303 | label: 'My Label', 304 | value: 'This is the content of the textarea' 305 | } 306 | }); 307 | 308 | expect(wrapper.first('textarea').value()).toBe('This is the content of the textarea'); 309 | }); 310 | }); 311 | -------------------------------------------------------------------------------- /tests/select-list.spec.ts: -------------------------------------------------------------------------------- 1 | import { suite, test, slow, timeout } from "mocha-typescript"; 2 | import {mount} from "avoriaz"; 3 | import SelectList from "./../src/select-list.ts"; 4 | import expect from 'expect'; 5 | import {StandardSlot} from "./resources.ts"; 6 | import sinon from 'sinon'; 7 | 8 | suite("SelectList", () => { 9 | test("it can set the name", () => { 10 | let wrapper: any = mount(SelectList, { 11 | propsData: { 12 | name: 'title' 13 | } 14 | }); 15 | 16 | expect(wrapper.first('select').hasAttribute('name')).toBe(true); 17 | expect(wrapper.first('select').getAttribute('name')).toBe('title'); 18 | }); 19 | 20 | test("the label gets output", () => { 21 | let wrapper: any = mount(SelectList, { 22 | propsData: { 23 | name: 'title', 24 | label: 'My Input' 25 | } 26 | }); 27 | 28 | expect(wrapper.first('label').text()).toBe('My Input'); 29 | }); 30 | 31 | test("if no label is specified it does not show it", () => { 32 | let wrapper: any = mount(SelectList, { 33 | propsData: { 34 | name: 'title' 35 | } 36 | }); 37 | expect(wrapper.find('label').length).toBe(0); 38 | }); 39 | 40 | test("if no placeholder is specified it does not show one", () => { 41 | let wrapper: any = mount(SelectList, { 42 | propsData: { 43 | name: 'title' 44 | } 45 | }); 46 | expect(wrapper.find('option').length).toBe(0); 47 | }); 48 | 49 | test("if a placeholder is specified it shows one", () => { 50 | let wrapper: any = mount(SelectList, { 51 | propsData: { 52 | name: 'title', 53 | placeholder: 'My Placeholder', 54 | value: '' 55 | } 56 | }); 57 | expect(wrapper.find('option').length).toBe(1); 58 | expect(wrapper.first('option').text()).toBe('My Placeholder'); 59 | }); 60 | 61 | test("if no helper is provided it does not show one", () => { 62 | let wrapper: any = mount(SelectList, { 63 | propsData: { 64 | name: 'title', 65 | } 66 | }); 67 | expect(wrapper.find('small.form-text').length).toBe(0); 68 | expect(wrapper.first('.form-group').hasClass('has-helper')).toBe(false); 69 | }); 70 | 71 | test("if helper text is provided it does show it", () => { 72 | let wrapper: any = mount(SelectList, { 73 | propsData: { 74 | name: 'title', 75 | helper: 'Please select a title' 76 | } 77 | }); 78 | 79 | expect(wrapper.find('small.form-text').length).toBe(1); 80 | expect(wrapper.first('small.form-text').text()).toBe("Please select a title"); 81 | expect(wrapper.first('.form-group').hasClass('has-helper')).toBe(true); 82 | }); 83 | 84 | test("if the field is not required it does not have the required attributes", () => { 85 | let wrapper: any = mount(SelectList, { 86 | propsData: { 87 | name: 'title', 88 | } 89 | }); 90 | 91 | expect(wrapper.find('span.required').length).toBe(0); 92 | expect(wrapper.first('select').hasAttribute('required')).toBe(false); 93 | }); 94 | 95 | test("if the field is required it have the required attributes", () => { 96 | let wrapper: any = mount(SelectList, { 97 | propsData: { 98 | name: 'title', 99 | label: 'Your Email', 100 | required: true 101 | } 102 | }); 103 | 104 | expect(wrapper.find('span.required').length).toBe(1); 105 | expect(wrapper.first('span.required').text()).toBe('*'); 106 | expect(wrapper.first('select').hasAttribute('required')).toBe(true); 107 | }); 108 | 109 | test("if the input is required but no label has been specified the input field has the required attribute", () => { 110 | let wrapper: any = mount(SelectList, { 111 | propsData: { 112 | name: 'title', 113 | required: true 114 | } 115 | }); 116 | 117 | expect(wrapper.first('select').hasAttribute('required')).toBe(true); 118 | }); 119 | 120 | test("the input can have the disabled attribute", () => { 121 | let wrapper: any = mount(SelectList, { 122 | propsData: { 123 | name: 'title', 124 | disabled: true, 125 | } 126 | }); 127 | 128 | expect(wrapper.first('select').hasAttribute('disabled')).toBe(true); 129 | }); 130 | 131 | test("by default the input does not have a disabled attribute", () => { 132 | let wrapper: any = mount(SelectList, { 133 | propsData: { 134 | name: 'title' 135 | } 136 | }); 137 | 138 | expect(wrapper.first('select').hasAttribute('disabled')).toBe(false); 139 | }); 140 | 141 | test("by default the class for the input is form-control", () => { 142 | let wrapper: any = mount(SelectList, { 143 | propsData: { 144 | name: 'title' 145 | } 146 | }); 147 | 148 | expect(wrapper.first('select').hasClass('form-control')).toBe(true); 149 | }); 150 | 151 | test("the form-control can be large", () => { 152 | let wrapper: any = mount(SelectList, { 153 | propsData: { 154 | name: 'title', 155 | large: true 156 | } 157 | }); 158 | 159 | expect(wrapper.first('select').hasClass('form-control')).toBe(true); 160 | expect(wrapper.first('select').hasClass('form-control-lg')).toBe(true); 161 | }); 162 | 163 | test("the form-control can be small", () => { 164 | let wrapper: any = mount(SelectList, { 165 | propsData: { 166 | name: 'title', 167 | small: true 168 | } 169 | }); 170 | 171 | expect(wrapper.first('select').hasClass('form-control')).toBe(true); 172 | expect(wrapper.first('select').hasClass('form-control-sm')).toBe(true); 173 | }); 174 | 175 | test("the input can be inline", () => { 176 | let wrapper: any = mount(SelectList, { 177 | propsData: { 178 | name: 'title', 179 | label: 'My Label', 180 | inline: true 181 | } 182 | }); 183 | 184 | expect(wrapper.find('.col-sm-4').length).toBe(1); 185 | expect(wrapper.first('.form-group').hasClass('row')).toBe(true); 186 | expect(wrapper.first('.col-sm-4').contains('label')).toBe(true); 187 | expect(wrapper.first('label').hasClass('col-form-label')).toBe(true); 188 | expect(wrapper.find('.col-sm-8').length).toBe(1); 189 | }); 190 | 191 | test("if the input is not inline, it does not have the row class", () => { 192 | let wrapper: any = mount(SelectList, { 193 | propsData: { 194 | name: 'title', 195 | label: 'My Label', 196 | } 197 | }); 198 | 199 | expect(wrapper.first('.form-group').hasClass('row')).toBe(false); 200 | }); 201 | 202 | test("the field can be marked as invalid", () => { 203 | let wrapper: any = mount(SelectList, { 204 | propsData: { 205 | name: 'title', 206 | label: 'My Label', 207 | invalid: true, 208 | errorMessage: "There was an error" 209 | } 210 | }); 211 | 212 | expect(wrapper.first('.form-control').hasClass('is-invalid')).toBe(true); 213 | expect(wrapper.first('.form-group').contains('.invalid-feedback')).toBe(true); 214 | expect(wrapper.first('.invalid-feedback').text()).toBe("There was an error"); 215 | }); 216 | 217 | test("the field does not show invalid feedback if it has not been marked as invalid", () => { 218 | let wrapper: any = mount(SelectList, { 219 | propsData: { 220 | name: 'title', 221 | label: 'My Label', 222 | errorMessage: "There was an error" 223 | } 224 | }); 225 | 226 | expect(wrapper.first('.form-control').hasClass('is-invalid')).toBe(false); 227 | expect(wrapper.first('.form-group').contains('.invalid-feedback')).toBe(false); 228 | }); 229 | 230 | test("the field does not show invalid feedback if no message has been passed", () => { 231 | let wrapper: any = mount(SelectList, { 232 | propsData: { 233 | name: 'title', 234 | label: 'My Label', 235 | invalid: true 236 | } 237 | }); 238 | 239 | expect(wrapper.first('.form-control').hasClass('is-invalid')).toBe(true); 240 | expect(wrapper.first('.invalid-feedback').contains('.invalid-feedback')).toBe(false); 241 | }); 242 | 243 | test("if no slots are being used the usingAddons property is false", () => { 244 | let wrapper: any = mount(SelectList, { 245 | propsData: { 246 | name: 'title', 247 | label: 'My Label', 248 | } 249 | }); 250 | 251 | expect(wrapper.vm.usingAddons).toBe(false); 252 | expect(wrapper.vm.slotExists('leftAddon')).toBe(false); 253 | expect(wrapper.vm.slotExists('rightAddon')).toBe(false); 254 | }); 255 | 256 | test("if the left add-on slot is being used the using addons property is true", () => { 257 | let wrapper: any = mount(SelectList, { 258 | propsData: { 259 | name: 'title', 260 | label: 'My Label', 261 | }, 262 | slots: { 263 | leftAddon: [StandardSlot] 264 | } 265 | }); 266 | 267 | expect(wrapper.vm.usingAddons).toBe(true); 268 | expect(wrapper.vm.slotExists('leftAddon')).toBe(true); 269 | expect(wrapper.vm.slotExists('rightAddon')).toBe(false); 270 | }); 271 | 272 | test("if the right add-on slot is being used the using addons property is true", () => { 273 | let wrapper: any = mount(SelectList, { 274 | propsData: { 275 | name: 'title', 276 | label: 'My Label', 277 | }, 278 | slots: { 279 | rightAddon: [StandardSlot] 280 | } 281 | }); 282 | 283 | expect(wrapper.vm.usingAddons).toBe(true); 284 | expect(wrapper.vm.slotExists('leftAddon')).toBe(false); 285 | expect(wrapper.vm.slotExists('rightAddon')).toBe(true); 286 | }); 287 | 288 | test("if the left add-on slot is being used it outputs the addon", () => { 289 | let wrapper: any = mount(SelectList, { 290 | propsData: { 291 | name: 'title', 292 | label: 'My Label', 293 | }, 294 | slots: { 295 | leftAddon: [StandardSlot] 296 | } 297 | }); 298 | 299 | expect(wrapper.first('.form-group').contains('.input-group')).toBe(true); 300 | expect(wrapper.first('.input-group').contains('.input-group-addon')).toBe(true); 301 | expect(wrapper.first('.input-group').contains('.form-text')).toBe(false); 302 | expect(wrapper.first('.input-group').contains('.invalid-feedback')).toBe(false); 303 | expect(wrapper.first('.input-group-addon').text()).toBe('addon'); 304 | }); 305 | 306 | test("if the right add-on slot is being used it outputs the addon", () => { 307 | let wrapper: any = mount(SelectList, { 308 | propsData: { 309 | name: 'title', 310 | label: 'My Label', 311 | }, 312 | slots: { 313 | rightAddon: [StandardSlot] 314 | } 315 | }); 316 | 317 | expect(wrapper.first('.form-group').contains('.input-group')).toBe(true); 318 | expect(wrapper.first('.input-group').contains('.input-group-addon')).toBe(true); 319 | expect(wrapper.first('.input-group').contains('.form-text')).toBe(false); 320 | expect(wrapper.first('.input-group').contains('.invalid-feedback')).toBe(false); 321 | expect(wrapper.first('.input-group-addon').text()).toBe('addon'); 322 | }); 323 | 324 | test("if the field has input groups and is large it has the correct classes", () => { 325 | let wrapper: any = mount(SelectList, { 326 | propsData: { 327 | name: 'title', 328 | label: 'My Label', 329 | large: true 330 | }, 331 | slots: { 332 | rightAddon: [StandardSlot] 333 | } 334 | }); 335 | 336 | expect(wrapper.first('.form-group').contains('.input-group')).toBe(true); 337 | expect(wrapper.first('.input-group').hasClass('input-group-lg')).toBe(true); 338 | expect(wrapper.first('.input-group').contains('.form-text')).toBe(false); 339 | expect(wrapper.first('.input-group').contains('.invalid-feedback')).toBe(false); 340 | expect(wrapper.first('.form-control').hasClass('form-control-lg')).toBe(false); 341 | }); 342 | 343 | test("if the field has input groups and is small it has the correct classes", () => { 344 | let wrapper: any = mount(SelectList, { 345 | propsData: { 346 | name: 'title', 347 | label: 'My Label', 348 | small: true 349 | }, 350 | slots: { 351 | rightAddon: [StandardSlot] 352 | } 353 | }); 354 | 355 | expect(wrapper.first('.form-group').contains('.input-group')).toBe(true); 356 | expect(wrapper.first('.input-group').hasClass('input-group-sm')).toBe(true); 357 | expect(wrapper.first('.input-group').contains('.form-text')).toBe(false); 358 | expect(wrapper.first('.input-group').contains('.invalid-feedback')).toBe(false); 359 | expect(wrapper.first('.form-control').hasClass('form-control-sm')).toBe(false); 360 | }); 361 | 362 | test("if the left button slot is being used it outputs the button", () => { 363 | let wrapper: any = mount(SelectList, { 364 | propsData: { 365 | name: 'title', 366 | label: 'My Label', 367 | }, 368 | slots: { 369 | leftButton: [StandardSlot] 370 | } 371 | }); 372 | 373 | expect(wrapper.first('.form-group').contains('.input-group')).toBe(true); 374 | expect(wrapper.first('.input-group').contains('.input-group-btn')).toBe(true); 375 | expect(wrapper.first('.input-group').contains('.form-text')).toBe(false); 376 | expect(wrapper.first('.input-group').contains('.invalid-feedback')).toBe(false); 377 | expect(wrapper.first('.input-group-btn').text()).toBe('addon'); 378 | }); 379 | 380 | test("if the right button slot is being used it outputs the button", () => { 381 | let wrapper: any = mount(SelectList, { 382 | propsData: { 383 | name: 'title', 384 | label: 'My Label', 385 | }, 386 | slots: { 387 | rightButton: [StandardSlot] 388 | } 389 | }); 390 | 391 | expect(wrapper.first('.form-group').contains('.input-group')).toBe(true); 392 | expect(wrapper.first('.input-group').contains('.input-group-btn')).toBe(true); 393 | expect(wrapper.first('.input-group').contains('.form-text')).toBe(false); 394 | expect(wrapper.first('.input-group').contains('.invalid-feedback')).toBe(false); 395 | expect(wrapper.first('.input-group-btn').text()).toBe('addon'); 396 | }); 397 | 398 | test("the helper text can be placed under the label", () => { 399 | let wrapper: any = mount(SelectList, { 400 | propsData: { 401 | name: 'title', 402 | label: 'My Label', 403 | helper: 'This is helper text', 404 | inline: true, 405 | metaUnderLabel: true 406 | } 407 | }); 408 | 409 | expect(wrapper.first('.col-sm-4').contains('.form-text')).toBe(true); 410 | expect(wrapper.first('.col-sm-8').contains('.form-text')).toBe(false); 411 | }); 412 | 413 | test("the error text can be placed under the label", () => { 414 | let wrapper: any = mount(SelectList, { 415 | propsData: { 416 | name: 'title', 417 | label: 'My Label', 418 | helper: 'This is helper text', 419 | inline: true, 420 | metaUnderLabel: true, 421 | invalid: true, 422 | errorMessage: 'There was an error' 423 | } 424 | }); 425 | 426 | expect(wrapper.first('.col-sm-4').contains('.invalid-feedback')).toBe(true); 427 | expect(wrapper.first('.col-sm-8').contains('.invalid-feedback')).toBe(false); 428 | }); 429 | 430 | test("it emits an event on change", () => { 431 | let wrapper: any = mount(SelectList, { 432 | propsData: { 433 | name: 'title', 434 | label: 'My Label' 435 | } 436 | }); 437 | const spy = sinon.spy(wrapper.vm, '$emit'); 438 | wrapper.first('.form-control').trigger("change"); 439 | expect(spy.args[0][0]).toBe('input') 440 | }); 441 | 442 | test("it can output the options for the list", () => { 443 | let options = [ 444 | { 445 | id: '1', 446 | label: 'Mr' 447 | }, 448 | { 449 | id: '2', 450 | label: 'Mrs' 451 | } 452 | ]; 453 | 454 | let wrapper: any = mount(SelectList, { 455 | propsData: { 456 | name: 'title', 457 | label: 'My Label', 458 | options: options 459 | } 460 | }); 461 | let foundOptions = wrapper.find('option'); 462 | 463 | expect(foundOptions.length).toBe(2); 464 | expect(foundOptions[0].getAttribute('value')).toBe('1'); 465 | expect(foundOptions[1].getAttribute('value')).toBe('2'); 466 | expect(foundOptions[0].text()).toBe('Mr'); 467 | expect(foundOptions[1].text()).toBe('Mrs'); 468 | }); 469 | 470 | test("the key name can be changed for options", () => { 471 | let options = [ 472 | { 473 | key: '1', 474 | label: 'Mr' 475 | }, 476 | { 477 | key: '2', 478 | label: 'Mrs' 479 | } 480 | ]; 481 | 482 | let wrapper: any = mount(SelectList, { 483 | propsData: { 484 | name: 'title', 485 | label: 'My Label', 486 | options: options, 487 | keyName: 'key' 488 | } 489 | }); 490 | let foundOptions = wrapper.find('option'); 491 | 492 | expect(foundOptions.length).toBe(2); 493 | expect(foundOptions[0].getAttribute('value')).toBe('1'); 494 | expect(foundOptions[1].getAttribute('value')).toBe('2'); 495 | expect(foundOptions[0].text()).toBe('Mr'); 496 | expect(foundOptions[1].text()).toBe('Mrs'); 497 | }); 498 | 499 | test("the label name can be changed for options", () => { 500 | let options = [ 501 | { 502 | id: '1', 503 | value: 'Mr' 504 | }, 505 | { 506 | id: '2', 507 | value: 'Mrs' 508 | } 509 | ]; 510 | 511 | let wrapper: any = mount(SelectList, { 512 | propsData: { 513 | name: 'title', 514 | label: 'My Label', 515 | options: options, 516 | labelName: 'value' 517 | } 518 | }); 519 | let foundOptions = wrapper.find('option'); 520 | 521 | expect(foundOptions.length).toBe(2); 522 | expect(foundOptions[0].getAttribute('value')).toBe('1'); 523 | expect(foundOptions[1].getAttribute('value')).toBe('2'); 524 | expect(foundOptions[0].text()).toBe('Mr'); 525 | expect(foundOptions[1].text()).toBe('Mrs'); 526 | }); 527 | 528 | test("the value of the input can be set", () => { 529 | let options = [ 530 | { 531 | id: '1', 532 | label: 'Mr' 533 | }, 534 | { 535 | id: '2', 536 | label: 'Mrs' 537 | } 538 | ]; 539 | 540 | let wrapper: any = mount(SelectList, { 541 | propsData: { 542 | name: 'title', 543 | label: 'My Label', 544 | options: options, 545 | value: '2' 546 | } 547 | }); 548 | 549 | expect(wrapper.first('select').value()).toBe('2'); 550 | }); 551 | }); 552 | -------------------------------------------------------------------------------- /tests/input-box.spec.ts: -------------------------------------------------------------------------------- 1 | import { suite, test, slow, timeout } from "mocha-typescript"; 2 | import {mount} from "avoriaz"; 3 | import InputBox from "./../src/input-box.ts"; 4 | import expect from 'expect'; 5 | import {StandardSlot} from "./resources.ts"; 6 | import sinon from 'sinon'; 7 | 8 | suite("InputBox", () => { 9 | test("it can set the name", () => { 10 | let wrapper: any = mount(InputBox, { 11 | propsData: { 12 | name: 'username' 13 | } 14 | }); 15 | 16 | expect(wrapper.first('input').hasAttribute('name')).toBe(true); 17 | expect(wrapper.first('input').getAttribute('name')).toBe('username'); 18 | }); 19 | 20 | test("if no type is specified it defaults to text", () => { 21 | let wrapper: any = mount(InputBox, { 22 | propsData: { 23 | name: 'username' 24 | } 25 | }); 26 | 27 | expect(wrapper.first('input').getAttribute('type')).toBe('text'); 28 | }); 29 | 30 | test("the type can be overwritten", () => { 31 | let wrapper: any = mount(InputBox, { 32 | propsData: { 33 | name: 'username', 34 | type: 'email' 35 | } 36 | }); 37 | 38 | expect(wrapper.first('input').getAttribute('type')).toBe('email'); 39 | }); 40 | 41 | test("the label gets output", () => { 42 | let wrapper: any = mount(InputBox, { 43 | propsData: { 44 | name: 'username', 45 | label: 'My Input' 46 | } 47 | }); 48 | 49 | expect(wrapper.first('label').text()).toBe('My Input'); 50 | }); 51 | 52 | test("if no label is specified it does not show it", () => { 53 | let wrapper: any = mount(InputBox, { 54 | propsData: { 55 | name: 'username' 56 | } 57 | }); 58 | expect(wrapper.find('label').length).toBe(0); 59 | }); 60 | 61 | test("if no placeholder is specified it does not show one", () => { 62 | let wrapper: any = mount(InputBox, { 63 | propsData: { 64 | name: 'username' 65 | } 66 | }); 67 | expect(wrapper.first('input').hasAttribute('placeholder')).toBe(false); 68 | }); 69 | 70 | test("if a placeholder is specified it shows one", () => { 71 | let wrapper: any = mount(InputBox, { 72 | propsData: { 73 | name: 'username', 74 | placeholder: 'My Placeholder' 75 | } 76 | }); 77 | expect(wrapper.first('input').hasAttribute('placeholder')).toBe(true); 78 | expect(wrapper.first('input').getAttribute('placeholder')).toBe('My Placeholder'); 79 | }); 80 | 81 | test("if no helper is provided it does not show one", () => { 82 | let wrapper: any = mount(InputBox, { 83 | propsData: { 84 | name: 'username', 85 | } 86 | }); 87 | expect(wrapper.find('small.form-text').length).toBe(0); 88 | expect(wrapper.first('.form-group').hasClass('has-helper')).toBe(false); 89 | }); 90 | 91 | test("if helper text is provided it does show it", () => { 92 | let wrapper: any = mount(InputBox, { 93 | propsData: { 94 | name: 'username', 95 | helper: 'Please enter your email address' 96 | } 97 | }); 98 | 99 | expect(wrapper.find('small.form-text').length).toBe(1); 100 | expect(wrapper.first('small.form-text').text()).toBe("Please enter your email address"); 101 | expect(wrapper.first('.form-group').hasClass('has-helper')).toBe(true); 102 | }); 103 | 104 | test("if the input is not required it does not have the required attributes", () => { 105 | let wrapper: any = mount(InputBox, { 106 | propsData: { 107 | name: 'username', 108 | } 109 | }); 110 | 111 | expect(wrapper.find('span.required').length).toBe(0); 112 | expect(wrapper.first('input').hasAttribute('required')).toBe(false); 113 | }); 114 | 115 | test("if the input is required it have the required attributes", () => { 116 | let wrapper: any = mount(InputBox, { 117 | propsData: { 118 | name: 'username', 119 | label: 'Your Email', 120 | required: true 121 | } 122 | }); 123 | 124 | expect(wrapper.find('span.required').length).toBe(1); 125 | expect(wrapper.first('span.required').text()).toBe('*'); 126 | expect(wrapper.first('input').hasAttribute('required')).toBe(true); 127 | }); 128 | 129 | test("if the input is required but no label has been specified the input field has the required attribute", () => { 130 | let wrapper: any = mount(InputBox, { 131 | propsData: { 132 | name: 'username', 133 | required: true 134 | } 135 | }); 136 | 137 | expect(wrapper.first('input').hasAttribute('required')).toBe(true); 138 | }); 139 | 140 | test("the input can have the readonly attribute", () => { 141 | let wrapper: any = mount(InputBox, { 142 | propsData: { 143 | name: 'username', 144 | readonly: true, 145 | } 146 | }); 147 | 148 | expect(wrapper.first('input').hasAttribute('readonly')).toBe(true); 149 | }); 150 | 151 | test("by default the input does not have a read only attribute", () => { 152 | let wrapper: any = mount(InputBox, { 153 | propsData: { 154 | name: 'username' 155 | } 156 | }); 157 | 158 | expect(wrapper.first('input').hasAttribute('readonly')).toBe(false); 159 | }); 160 | 161 | test("by default the class for the input is form-control", () => { 162 | let wrapper: any = mount(InputBox, { 163 | propsData: { 164 | name: 'username' 165 | } 166 | }); 167 | 168 | expect(wrapper.first('input').hasClass('form-control')).toBe(true); 169 | }); 170 | 171 | test("the form-control can be large", () => { 172 | let wrapper: any = mount(InputBox, { 173 | propsData: { 174 | name: 'username', 175 | large: true 176 | } 177 | }); 178 | 179 | expect(wrapper.first('input').hasClass('form-control')).toBe(true); 180 | expect(wrapper.first('input').hasClass('form-control-lg')).toBe(true); 181 | }); 182 | 183 | test("the form-control can be small", () => { 184 | let wrapper: any = mount(InputBox, { 185 | propsData: { 186 | name: 'username', 187 | small: true 188 | } 189 | }); 190 | 191 | expect(wrapper.first('input').hasClass('form-control')).toBe(true); 192 | expect(wrapper.first('input').hasClass('form-control-sm')).toBe(true); 193 | }); 194 | 195 | test("the form-control can be plain text mode", () => { 196 | let wrapper: any = mount(InputBox, { 197 | propsData: { 198 | name: 'username', 199 | plainText: true 200 | } 201 | }); 202 | 203 | expect(wrapper.first('input').hasClass('form-control')).toBe(false); 204 | expect(wrapper.first('input').hasClass('form-control-plaintext')).toBe(true); 205 | }); 206 | 207 | test("the input box can be inline", () => { 208 | let wrapper: any = mount(InputBox, { 209 | propsData: { 210 | name: 'username', 211 | label: 'My Label', 212 | inline: true 213 | } 214 | }); 215 | 216 | expect(wrapper.find('.col-sm-4').length).toBe(1); 217 | expect(wrapper.first('.form-group').hasClass('row')).toBe(true); 218 | expect(wrapper.first('.col-sm-4').contains('label')).toBe(true); 219 | expect(wrapper.first('label').hasClass('col-form-label')).toBe(true); 220 | expect(wrapper.find('.col-sm-8').length).toBe(1); 221 | }); 222 | 223 | test("if the input is not inline, it does not have the row class", () => { 224 | let wrapper: any = mount(InputBox, { 225 | propsData: { 226 | name: 'username', 227 | label: 'My Label', 228 | } 229 | }); 230 | 231 | expect(wrapper.first('.form-group').hasClass('row')).toBe(false); 232 | }); 233 | 234 | test("the field can be marked as invalid", () => { 235 | let wrapper: any = mount(InputBox, { 236 | propsData: { 237 | name: 'username', 238 | label: 'My Label', 239 | invalid: true, 240 | errorMessage: "There was an error" 241 | } 242 | }); 243 | 244 | expect(wrapper.first('.form-control').hasClass('is-invalid')).toBe(true); 245 | expect(wrapper.first('.form-group').contains('.invalid-feedback')).toBe(true); 246 | expect(wrapper.first('.invalid-feedback').text()).toBe("There was an error"); 247 | }); 248 | 249 | test("the field does not show invalid feedback if it has not been marked as invalid", () => { 250 | let wrapper: any = mount(InputBox, { 251 | propsData: { 252 | name: 'username', 253 | label: 'My Label', 254 | errorMessage: "There was an error" 255 | } 256 | }); 257 | 258 | expect(wrapper.first('.form-control').hasClass('is-invalid')).toBe(false); 259 | expect(wrapper.first('.form-group').contains('.invalid-feedback')).toBe(false); 260 | }); 261 | 262 | test("the field does not show invalid feedback if no message has been passed", () => { 263 | let wrapper: any = mount(InputBox, { 264 | propsData: { 265 | name: 'username', 266 | label: 'My Label', 267 | invalid: true 268 | } 269 | }); 270 | 271 | expect(wrapper.first('.form-control').hasClass('is-invalid')).toBe(true); 272 | expect(wrapper.first('.invalid-feedback').contains('.invalid-feedback')).toBe(false); 273 | }); 274 | 275 | test("if no slots are being used the usingAddons property is false", () => { 276 | let wrapper: any = mount(InputBox, { 277 | propsData: { 278 | name: 'username', 279 | label: 'My Label', 280 | } 281 | }); 282 | 283 | expect(wrapper.vm.usingAddons).toBe(false); 284 | expect(wrapper.vm.slotExists('leftAddon')).toBe(false); 285 | expect(wrapper.vm.slotExists('rightAddon')).toBe(false); 286 | }); 287 | 288 | test("if the left add-on slot is being used the using addons property is true", () => { 289 | let wrapper: any = mount(InputBox, { 290 | propsData: { 291 | name: 'username', 292 | label: 'My Label', 293 | }, 294 | slots: { 295 | leftAddon: [StandardSlot] 296 | } 297 | }); 298 | 299 | expect(wrapper.vm.usingAddons).toBe(true); 300 | expect(wrapper.vm.slotExists('leftAddon')).toBe(true); 301 | expect(wrapper.vm.slotExists('rightAddon')).toBe(false); 302 | }); 303 | 304 | test("if the right add-on slot is being used the using addons property is true", () => { 305 | let wrapper: any = mount(InputBox, { 306 | propsData: { 307 | name: 'username', 308 | label: 'My Label', 309 | }, 310 | slots: { 311 | rightAddon: [StandardSlot] 312 | } 313 | }); 314 | 315 | expect(wrapper.vm.usingAddons).toBe(true); 316 | expect(wrapper.vm.slotExists('leftAddon')).toBe(false); 317 | expect(wrapper.vm.slotExists('rightAddon')).toBe(true); 318 | }); 319 | 320 | test("if the left add-on slot is being used it outputs the addon", () => { 321 | let wrapper: any = mount(InputBox, { 322 | propsData: { 323 | name: 'username', 324 | label: 'My Label', 325 | }, 326 | slots: { 327 | leftAddon: [StandardSlot] 328 | } 329 | }); 330 | 331 | expect(wrapper.first('.form-group').contains('.input-group')).toBe(true); 332 | expect(wrapper.first('.input-group').contains('.input-group-addon')).toBe(true); 333 | expect(wrapper.first('.input-group').contains('.form-text')).toBe(false); 334 | expect(wrapper.first('.input-group').contains('.invalid-feedback')).toBe(false); 335 | expect(wrapper.first('.input-group-addon').text()).toBe('addon'); 336 | }); 337 | 338 | test("if the right add-on slot is being used it outputs the addon", () => { 339 | let wrapper: any = mount(InputBox, { 340 | propsData: { 341 | name: 'username', 342 | label: 'My Label', 343 | }, 344 | slots: { 345 | rightAddon: [StandardSlot] 346 | } 347 | }); 348 | 349 | expect(wrapper.first('.form-group').contains('.input-group')).toBe(true); 350 | expect(wrapper.first('.input-group').contains('.input-group-addon')).toBe(true); 351 | expect(wrapper.first('.input-group').contains('.form-text')).toBe(false); 352 | expect(wrapper.first('.input-group').contains('.invalid-feedback')).toBe(false); 353 | expect(wrapper.first('.input-group-addon').text()).toBe('addon'); 354 | }); 355 | 356 | test("if the field has input groups and is large it has the correct classes", () => { 357 | let wrapper: any = mount(InputBox, { 358 | propsData: { 359 | name: 'username', 360 | label: 'My Label', 361 | large: true 362 | }, 363 | slots: { 364 | rightAddon: [StandardSlot] 365 | } 366 | }); 367 | 368 | expect(wrapper.first('.form-group').contains('.input-group')).toBe(true); 369 | expect(wrapper.first('.input-group').hasClass('input-group-lg')).toBe(true); 370 | expect(wrapper.first('.input-group').contains('.form-text')).toBe(false); 371 | expect(wrapper.first('.input-group').contains('.invalid-feedback')).toBe(false); 372 | expect(wrapper.first('.form-control').hasClass('form-control-lg')).toBe(false); 373 | }); 374 | 375 | test("if the field has input groups and is small it has the correct classes", () => { 376 | let wrapper: any = mount(InputBox, { 377 | propsData: { 378 | name: 'username', 379 | label: 'My Label', 380 | small: true 381 | }, 382 | slots: { 383 | rightAddon: [StandardSlot] 384 | } 385 | }); 386 | 387 | expect(wrapper.first('.form-group').contains('.input-group')).toBe(true); 388 | expect(wrapper.first('.input-group').hasClass('input-group-sm')).toBe(true); 389 | expect(wrapper.first('.input-group').contains('.form-text')).toBe(false); 390 | expect(wrapper.first('.input-group').contains('.invalid-feedback')).toBe(false); 391 | expect(wrapper.first('.form-control').hasClass('form-control-sm')).toBe(false); 392 | }); 393 | 394 | test("if the left button slot is being used it outputs the button", () => { 395 | let wrapper: any = mount(InputBox, { 396 | propsData: { 397 | name: 'username', 398 | label: 'My Label', 399 | }, 400 | slots: { 401 | leftButton: [StandardSlot] 402 | } 403 | }); 404 | 405 | expect(wrapper.first('.form-group').contains('.input-group')).toBe(true); 406 | expect(wrapper.first('.input-group').contains('.input-group-btn')).toBe(true); 407 | expect(wrapper.first('.input-group').contains('.form-text')).toBe(false); 408 | expect(wrapper.first('.input-group').contains('.invalid-feedback')).toBe(false); 409 | expect(wrapper.first('.input-group-btn').text()).toBe('addon'); 410 | }); 411 | 412 | test("if the right button slot is being used it outputs the button", () => { 413 | let wrapper: any = mount(InputBox, { 414 | propsData: { 415 | name: 'username', 416 | label: 'My Label', 417 | }, 418 | slots: { 419 | rightButton: [StandardSlot] 420 | } 421 | }); 422 | 423 | expect(wrapper.first('.form-group').contains('.input-group')).toBe(true); 424 | expect(wrapper.first('.input-group').contains('.input-group-btn')).toBe(true); 425 | expect(wrapper.first('.input-group').contains('.form-text')).toBe(false); 426 | expect(wrapper.first('.input-group').contains('.invalid-feedback')).toBe(false); 427 | expect(wrapper.first('.input-group-btn').text()).toBe('addon'); 428 | }); 429 | 430 | test("the helper text can be placed under the label", () => { 431 | let wrapper: any = mount(InputBox, { 432 | propsData: { 433 | name: 'username', 434 | label: 'My Label', 435 | helper: 'This is helper text', 436 | inline: true, 437 | metaUnderLabel: true 438 | } 439 | }); 440 | 441 | expect(wrapper.first('.col-sm-4').contains('.form-text')).toBe(true); 442 | expect(wrapper.first('.col-sm-8').contains('.form-text')).toBe(false); 443 | }); 444 | 445 | test("the error text can be placed under the label", () => { 446 | let wrapper: any = mount(InputBox, { 447 | propsData: { 448 | name: 'username', 449 | label: 'My Label', 450 | helper: 'This is helper text', 451 | inline: true, 452 | metaUnderLabel: true, 453 | invalid: true, 454 | errorMessage: 'There was an error' 455 | } 456 | }); 457 | 458 | expect(wrapper.first('.col-sm-4').contains('.invalid-feedback')).toBe(true); 459 | expect(wrapper.first('.col-sm-8').contains('.invalid-feedback')).toBe(false); 460 | }); 461 | 462 | test("when the enter key is pressed it emits an event", () => { 463 | let wrapper: any = mount(InputBox, { 464 | propsData: { 465 | name: 'username', 466 | label: 'My Label' 467 | } 468 | }); 469 | const spy = sinon.spy(wrapper.vm, '$emit'); 470 | wrapper.first('.form-control').trigger("keyup.enter"); 471 | expect(spy.args[0][0]).toBe('enter') 472 | }); 473 | 474 | test("it emits an event on input", () => { 475 | let wrapper: any = mount(InputBox, { 476 | propsData: { 477 | name: 'username', 478 | label: 'My Label' 479 | } 480 | }); 481 | const spy = sinon.spy(wrapper.vm, '$emit'); 482 | wrapper.first('.form-control').trigger("input"); 483 | expect(spy.args[0][0]).toBe('input') 484 | }); 485 | 486 | test("the value of the input can be set", () => { 487 | let wrapper: any = mount(InputBox, { 488 | propsData: { 489 | name: 'username', 490 | label: 'My Label', 491 | value: 'test' 492 | } 493 | }); 494 | 495 | expect(wrapper.first('.form-control').value()).toBe('test'); 496 | }); 497 | 498 | 499 | test("the max length of the input can be set", () => { 500 | let wrapper: any = mount(InputBox, { 501 | propsData: { 502 | name: 'username', 503 | label: 'My Label', 504 | value: 'test', 505 | maxLength: 10 506 | } 507 | }); 508 | 509 | expect(wrapper.first('input').hasAttribute('maxlength')).toBe(true); 510 | expect(wrapper.first('input').getAttribute('maxlength')).toBe("10"); 511 | }); 512 | 513 | test("the max length of the input is set by default", () => { 514 | let wrapper: any = mount(InputBox, { 515 | propsData: { 516 | name: 'username', 517 | label: 'My Label', 518 | value: 'test' 519 | } 520 | }); 521 | 522 | expect(wrapper.first('input').hasAttribute('maxlength')).toBe(true); 523 | expect(wrapper.first('input').getAttribute('maxlength')).toBe("524288"); 524 | }); 525 | 526 | test("the autocomplete of the input is not rendered by default", () => { 527 | let wrapper: any = mount(InputBox, { 528 | propsData: { 529 | name: 'username', 530 | label: 'My Label', 531 | value: 'test' 532 | } 533 | }); 534 | 535 | expect(wrapper.first('input').hasAttribute('autocomplete')).toBe(true); 536 | expect(wrapper.first('input').getAttribute('autocomplete')).toBe('on'); 537 | }); 538 | 539 | test("the autocomplete of the input can be disabled", () => { 540 | let wrapper: any = mount(InputBox, { 541 | propsData: { 542 | name: 'username', 543 | label: 'My Label', 544 | value: 'test', 545 | autoComplete: false 546 | } 547 | }); 548 | 549 | expect(wrapper.first('input').hasAttribute('autocomplete')).toBe(true); 550 | expect(wrapper.first('input').getAttribute('autocomplete')).toBe('off'); 551 | }); 552 | }); 553 | --------------------------------------------------------------------------------