├── .babelrc ├── .editorconfig ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── src ├── App.spec.js ├── App.vue ├── features │ └── auth │ │ ├── main.vue │ │ ├── sign-in.spec.js │ │ ├── sign-in.vue │ │ ├── sign-up.vue │ │ └── styles.css ├── main.js └── plugins │ └── event-bus.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-3" 5 | ], 6 | "env": { 7 | "test": { 8 | "presets": [ 9 | ["env", { "targets": { "node": "current" }}] 10 | ] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | yarn-error.log 6 | 7 | # Editor directories and files 8 | .idea 9 | *.suo 10 | *.ntvs* 11 | *.njsproj 12 | *.sln 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # login 2 | 3 | > A Vue.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | ``` 17 | 18 | For detailed explanation on how things work, consult the [docs for vue-loader](http://vuejs.github.io/vue-loader). 19 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | login 6 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "login", 3 | "description": "A Vue.js project", 4 | "version": "1.0.0", 5 | "author": "Fabio Vedovelli ", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", 10 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules", 11 | "test": "jest" 12 | }, 13 | "dependencies": { 14 | "vue": "^2.5.11", 15 | "vuelidate": "^0.6.1" 16 | }, 17 | "browserslist": [ 18 | "> 1%", 19 | "last 2 versions", 20 | "not ie <= 8" 21 | ], 22 | "devDependencies": { 23 | "@vue/test-utils": "^1.0.0-beta.12", 24 | "babel-core": "^6.26.0", 25 | "babel-loader": "^7.1.2", 26 | "babel-preset-env": "^1.6.0", 27 | "babel-preset-stage-3": "^6.24.1", 28 | "cross-env": "^5.0.5", 29 | "css-loader": "^0.28.7", 30 | "file-loader": "^1.1.4", 31 | "jest": "^22.4.2", 32 | "vue-jest": "^2.1.0", 33 | "vue-loader": "^13.0.5", 34 | "vue-template-compiler": "^2.4.4", 35 | "webpack": "^3.6.0", 36 | "webpack-dev-server": "^2.9.1" 37 | }, 38 | "jest": { 39 | "moduleNameMapper": { 40 | "^@/(.*)$": "/src/$1" 41 | }, 42 | "moduleFileExtensions": [ 43 | "js", 44 | "json", 45 | "vue" 46 | ], 47 | "transform": { 48 | ".*\\.(vue)$": "/node_modules/vue-jest", 49 | "^.+\\.js$": "/node_modules/babel-jest" 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/App.spec.js: -------------------------------------------------------------------------------- 1 | 2 | import { shallow } from '@vue/test-utils' 3 | import App from './App' 4 | 5 | describe('App', () => { 6 | test('é uma instância do Vue', () => { 7 | const wrapper = shallow(App) 8 | expect(wrapper.isVueInstance()).toBeTruthy() 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /src/features/auth/main.vue: -------------------------------------------------------------------------------- 1 | 2 | 30 | 31 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/features/auth/sign-in.spec.js: -------------------------------------------------------------------------------- 1 | 2 | import Vue from 'vue' 3 | import Vuelidate from 'vuelidate' 4 | import EventBus from '@/plugins/event-bus' 5 | import { shallow } from '@vue/test-utils' 6 | 7 | Vue.use(EventBus) 8 | Vue.use(Vuelidate) 9 | 10 | import Signin from './sign-in' 11 | 12 | describe('Signin', () => { 13 | let wrapper 14 | 15 | beforeAll(() => { 16 | wrapper = shallow(Signin) 17 | }) 18 | 19 | test('Verifica o estado da propriedade username no validation model quando input é preenchido', () => { 20 | setData(wrapper) 21 | const field = wrapper.find('#sign-in-user') 22 | field.trigger('input') 23 | expect(wrapper.vm.$v.username.$dirty).toBeTruthy() 24 | resetData(wrapper) 25 | }) 26 | 27 | test('Todas as propriedades e ferramentas voltam ao estado inicial quando reset() for executado', () => { 28 | const { username, email, password } = wrapper.vm.$data 29 | 30 | setData(wrapper) 31 | resetData(wrapper) 32 | 33 | expect(username).toBe('') 34 | expect(email).toBe('') 35 | expect(password).toBe('') 36 | expect(wrapper.vm.$v.$invalid).toBeTruthy() 37 | }) 38 | 39 | test('As propriedades corretas estão presentes no estado do componente', () => { 40 | const expected = ['username', 'email', 'password', 'keepSignedIn'] 41 | const received = Object.keys(wrapper.vm.$data) 42 | expect(expected).toEqual(received) 43 | }) 44 | 45 | test('O evento do-sign-in é disparado quando o vm.submit() é executado', () => { 46 | setData(wrapper) 47 | wrapper.vm.submit() 48 | const data = wrapper.emitted('do-sign-in')[0][0] 49 | expect(data).toEqual({ 50 | email: 'vedovelli@gmail.com', 51 | username: 'vedovelli', 52 | password: 123456, 53 | keepSignedIn: true 54 | }) 55 | }) 56 | 57 | test('Se o estado do validation model é $invalid == false quando todos os campos obrigatórios forem preenchidos', () => { 58 | setData(wrapper) 59 | expect(wrapper.vm.$v.$invalid).toBeFalsy() 60 | }) 61 | 62 | test('Se o estado do validation model é inicialmente $invalid == true', () => { 63 | resetData(wrapper) 64 | expect(wrapper.vm.$v.$invalid).toBeTruthy() 65 | }) 66 | 67 | test('Componente é uma instância do Vue', () => { 68 | expect(wrapper.isVueInstance()).toBeTruthy() 69 | }) 70 | 71 | const setData = wrapper => wrapper.setData({ 72 | username: 'vedovelli', 73 | email: 'vedovelli@gmail.com', 74 | password: 123456 75 | }) 76 | 77 | const resetData = wrapper => wrapper.setData({ 78 | username: '', 79 | email: '', 80 | password: '' 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /src/features/auth/sign-in.vue: -------------------------------------------------------------------------------- 1 | 2 | 43 | 44 | 85 | -------------------------------------------------------------------------------- /src/features/auth/sign-up.vue: -------------------------------------------------------------------------------- 1 | 2 | 52 | 53 | 80 | -------------------------------------------------------------------------------- /src/features/auth/styles.css: -------------------------------------------------------------------------------- 1 | .login-wrap{ 2 | width:100%; 3 | margin:auto; 4 | max-width:525px; 5 | min-height:670px; 6 | position:relative; 7 | background:url(https://raw.githubusercontent.com/khadkamhn/day-01-login-form/master/img/bg.jpg) no-repeat center; 8 | box-shadow:0 12px 15px 0 rgba(0,0,0,.24),0 17px 50px 0 rgba(0,0,0,.19); 9 | } 10 | .login-html{ 11 | width:100%; 12 | height:100%; 13 | position:absolute; 14 | padding:90px 70px 50px 70px; 15 | background:rgba(40,57,101,.9); 16 | } 17 | .login-html .sign-in-htm, 18 | .login-html .sign-up-htm{ 19 | top:0; 20 | left:0; 21 | right:0; 22 | bottom:0; 23 | position:absolute; 24 | transform:rotateY(180deg); 25 | backface-visibility:hidden; 26 | transition:all .4s linear; 27 | } 28 | .login-html .sign-in, 29 | .login-html .sign-up, 30 | .login-form .group .check{ 31 | display:none; 32 | } 33 | .login-html .tab, 34 | .login-form .group .label, 35 | .login-form .group .button{ 36 | text-transform:uppercase; 37 | } 38 | .login-html .tab{ 39 | font-size:22px; 40 | margin-right:15px; 41 | padding-bottom:5px; 42 | margin:0 15px 10px 0; 43 | display:inline-block; 44 | border-bottom:2px solid transparent; 45 | } 46 | .login-html .sign-in:checked + .tab, 47 | .login-html .sign-up:checked + .tab{ 48 | color:#fff; 49 | border-color:#1161ee; 50 | } 51 | .login-form{ 52 | min-height:345px; 53 | position:relative; 54 | perspective:1000px; 55 | transform-style:preserve-3d; 56 | } 57 | .login-form .group{ 58 | margin-bottom:15px; 59 | } 60 | 61 | .login-form .group input[type="text"].invalid, 62 | .login-form .group input[type="password"].invalid { 63 | border: red 1px solid; 64 | } 65 | 66 | .login-form .group label.invalid { 67 | color: red; 68 | } 69 | 70 | .login-form .group .label, 71 | .login-form .group .input, 72 | .login-form .group .button{ 73 | width:100%; 74 | color:#fff; 75 | display:block; 76 | } 77 | .login-form .group .input, 78 | .login-form .group .button{ 79 | border:none; 80 | padding:15px 20px; 81 | border-radius:25px; 82 | background:rgba(255,255,255,.1); 83 | } 84 | .login-form .group input[data-type="password"]{ 85 | text-security:circle; 86 | -webkit-text-security:circle; 87 | } 88 | .login-form .group .label{ 89 | color:#aaa; 90 | font-size:12px; 91 | } 92 | .login-form .group .button{ 93 | background:#1161ee; 94 | } 95 | .login-form .group label .icon{ 96 | width:15px; 97 | height:15px; 98 | border-radius:2px; 99 | position:relative; 100 | display:inline-block; 101 | background:rgba(255,255,255,.1); 102 | } 103 | .login-form .group label .icon:before, 104 | .login-form .group label .icon:after{ 105 | content:''; 106 | width:10px; 107 | height:2px; 108 | background:#fff; 109 | position:absolute; 110 | transition:all .2s ease-in-out 0s; 111 | } 112 | .login-form .group label .icon:before{ 113 | left:3px; 114 | width:5px; 115 | bottom:6px; 116 | transform:scale(0) rotate(0); 117 | } 118 | .login-form .group label .icon:after{ 119 | top:6px; 120 | right:0; 121 | transform:scale(0) rotate(0); 122 | } 123 | .login-form .group .check:checked + label{ 124 | color:#fff; 125 | } 126 | .login-form .group .check:checked + label .icon{ 127 | background:#1161ee; 128 | } 129 | .login-form .group .check:checked + label .icon:before{ 130 | transform:scale(1) rotate(45deg); 131 | } 132 | .login-form .group .check:checked + label .icon:after{ 133 | transform:scale(1) rotate(-45deg); 134 | } 135 | .login-html .sign-in:checked + .tab + .sign-up + .tab + .login-form .sign-in-htm{ 136 | transform:rotate(0); 137 | } 138 | .login-html .sign-up:checked + .tab + .login-form .sign-up-htm{ 139 | transform:rotate(0); 140 | } 141 | 142 | .hr{ 143 | height:2px; 144 | margin:60px 0 50px 0; 145 | background:rgba(255,255,255,.2); 146 | } 147 | .foot-lnk{ 148 | text-align:center; 149 | } 150 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import EventBus from './plugins/event-bus' 4 | import Vuelidate from 'vuelidate' 5 | 6 | Vue.use(EventBus) 7 | Vue.use(Vuelidate) 8 | 9 | new Vue({ 10 | el: '#app', 11 | render: h => h(App) 12 | }) 13 | -------------------------------------------------------------------------------- /src/plugins/event-bus.js: -------------------------------------------------------------------------------- 1 | 2 | import Vue from 'Vue' 3 | 4 | const bus = new Vue() 5 | 6 | export default function install(Vue) { 7 | Object.defineProperties(Vue.prototype, { 8 | $bus: { 9 | get() { 10 | return bus 11 | } 12 | } 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: './src/main.js', 6 | output: { 7 | path: path.resolve(__dirname, './dist'), 8 | publicPath: '/dist/', 9 | filename: 'build.js' 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.css$/, 15 | use: [ 16 | 'vue-style-loader', 17 | 'css-loader' 18 | ], 19 | }, { 20 | test: /\.vue$/, 21 | loader: 'vue-loader', 22 | options: { 23 | loaders: { 24 | } 25 | // other vue-loader options go here 26 | } 27 | }, 28 | { 29 | test: /\.js$/, 30 | loader: 'babel-loader', 31 | exclude: /node_modules/ 32 | }, 33 | { 34 | test: /\.(png|jpg|gif|svg)$/, 35 | loader: 'file-loader', 36 | options: { 37 | name: '[name].[ext]?[hash]' 38 | } 39 | } 40 | ] 41 | }, 42 | resolve: { 43 | alias: { 44 | 'vue$': 'vue/dist/vue.esm.js' 45 | }, 46 | extensions: ['*', '.js', '.vue', '.json'] 47 | }, 48 | devServer: { 49 | historyApiFallback: true, 50 | noInfo: true, 51 | overlay: true 52 | }, 53 | performance: { 54 | hints: false 55 | }, 56 | devtool: '#eval-source-map' 57 | } 58 | 59 | if (process.env.NODE_ENV === 'production') { 60 | module.exports.devtool = '#source-map' 61 | // http://vue-loader.vuejs.org/en/workflow/production.html 62 | module.exports.plugins = (module.exports.plugins || []).concat([ 63 | new webpack.DefinePlugin({ 64 | 'process.env': { 65 | NODE_ENV: '"production"' 66 | } 67 | }), 68 | new webpack.optimize.UglifyJsPlugin({ 69 | sourceMap: true, 70 | compress: { 71 | warnings: false 72 | } 73 | }), 74 | new webpack.LoaderOptionsPlugin({ 75 | minimize: true 76 | }) 77 | ]) 78 | } 79 | --------------------------------------------------------------------------------