diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..13f0e47 --- /dev/null +++ b/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + ["env", { "modules": false }], + "stage-2" + ], + "plugins": ["transform-runtime"], + "comments": false, + "env": { + "test": { + "presets": ["env", "stage-2"], + "plugins": [ "istanbul" ] + } + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9d08a1a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e1d210 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +node_modules/ +dist/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +test/unit/coverage +test/e2e/reports +selenium-debug.log diff --git a/.postcssrc.js b/.postcssrc.js new file mode 100644 index 0000000..ea9a5ab --- /dev/null +++ b/.postcssrc.js @@ -0,0 +1,8 @@ +// https://github.com/michael-ciniawsky/postcss-load-config + +module.exports = { + "plugins": { + // to edit target browsers: use "browserlist" field in package.json + "autoprefixer": {} + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..c1fe31f --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# vue2-vuex + +> A Vue.js project + +## Build Setup + +``` bash +# install dependencies +npm install + +# serve with hot reload at localhost:8080 +npm run dev + +# build for production with minification +npm run build + +# build for production and view the bundle analyzer report +npm run build --report + +# run unit tests +npm run unit + +# run e2e tests +npm run e2e + +# run all tests +npm test +``` + +For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). diff --git a/build/build.js b/build/build.js new file mode 100644 index 0000000..6b8add1 --- /dev/null +++ b/build/build.js @@ -0,0 +1,35 @@ +require('./check-versions')() + +process.env.NODE_ENV = 'production' + +var ora = require('ora') +var rm = require('rimraf') +var path = require('path') +var chalk = require('chalk') +var webpack = require('webpack') +var config = require('../config') +var webpackConfig = require('./webpack.prod.conf') + +var spinner = ora('building for production...') +spinner.start() + +rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { + if (err) throw err + webpack(webpackConfig, function (err, stats) { + spinner.stop() + if (err) throw err + process.stdout.write(stats.toString({ + colors: true, + modules: false, + children: false, + chunks: false, + chunkModules: false + }) + '\n\n') + + console.log(chalk.cyan(' Build complete.\n')) + console.log(chalk.yellow( + ' Tip: built files are meant to be served over an HTTP server.\n' + + ' Opening index.html over file:// won\'t work.\n' + )) + }) +}) diff --git a/build/check-versions.js b/build/check-versions.js new file mode 100644 index 0000000..100f3a0 --- /dev/null +++ b/build/check-versions.js @@ -0,0 +1,48 @@ +var chalk = require('chalk') +var semver = require('semver') +var packageConfig = require('../package.json') +var shell = require('shelljs') +function exec (cmd) { + return require('child_process').execSync(cmd).toString().trim() +} + +var versionRequirements = [ + { + name: 'node', + currentVersion: semver.clean(process.version), + versionRequirement: packageConfig.engines.node + }, +] + +if (shell.which('npm')) { + versionRequirements.push({ + name: 'npm', + currentVersion: exec('npm --version'), + versionRequirement: packageConfig.engines.npm + }) +} + +module.exports = function () { + var warnings = [] + for (var i = 0; i < versionRequirements.length; i++) { + var mod = versionRequirements[i] + if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { + warnings.push(mod.name + ': ' + + chalk.red(mod.currentVersion) + ' should be ' + + chalk.green(mod.versionRequirement) + ) + } + } + + if (warnings.length) { + console.log('') + console.log(chalk.yellow('To use this template, you must update following to modules:')) + console.log() + for (var i = 0; i < warnings.length; i++) { + var warning = warnings[i] + console.log(' ' + warning) + } + console.log() + process.exit(1) + } +} diff --git a/build/dev-client.js b/build/dev-client.js new file mode 100644 index 0000000..18aa1e2 --- /dev/null +++ b/build/dev-client.js @@ -0,0 +1,9 @@ +/* eslint-disable */ +require('eventsource-polyfill') +var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') + +hotClient.subscribe(function (event) { + if (event.action === 'reload') { + window.location.reload() + } +}) diff --git a/build/dev-server.js b/build/dev-server.js new file mode 100644 index 0000000..f30e489 --- /dev/null +++ b/build/dev-server.js @@ -0,0 +1,131 @@ +require('./check-versions')() + +var config = require('../config') +if (!process.env.NODE_ENV) { + process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) +} + +var opn = require('opn') +var path = require('path') +var express = require('express') +var webpack = require('webpack') +var proxyMiddleware = require('http-proxy-middleware') +var webpackConfig = process.env.NODE_ENV === 'testing' + ? require('./webpack.prod.conf') + : require('./webpack.dev.conf') + +// default port where dev server listens for incoming traffic +var port = process.env.PORT || config.dev.port +// automatically open browser, if not set will be false +var autoOpenBrowser = !!config.dev.autoOpenBrowser +// Define HTTP proxies to your custom API backend +// https://github.com/chimurai/http-proxy-middleware +var proxyTable = config.dev.proxyTable + +var app = express() + +// http请求模块 +const superagent = require('superagent') + +// 模拟后台假数据 +let appData = require('../mockdata.json') +let self = appData.self +let friend = appData.friend + +let apiRoutes = express.Router() + +apiRoutes.get('/self', (req, res) => { + res.json({data: self}) +}) + +apiRoutes.get('/friends', (req, res) => { + res.json({data: friend}) +}) + +// 测试salesforce接口 +apiRoutes.get('/getBusinessCard', (req, res) => { + let response = res; + let url = 'https://ap4.salesforce.com/services/data/v37.0/query?q=' + encodeURIComponent('SELECT Id, Name, Mobile__c, Company__c FROM Business_Card__c'); + let sessionId = '00D6F000001v8ld!ARwAQH6bia1sVzA.4jFWTuvZUiSNdM8aFu4ogGYYWGVmjrT.UF0zhFvsQnM5tEPwYu89hFCrBheKdFKQtoRkmEroBXj2.n0T'; + superagent.get(url) + .set('Authorization', 'Bearer ' + sessionId) + .end((err, res) => { + if (err) { + console.log(err) + } + response.json({ + data: res.text + }) + }) +}) + +app.use('/api', apiRoutes) + + +var compiler = webpack(webpackConfig) + +var devMiddleware = require('webpack-dev-middleware')(compiler, { + publicPath: webpackConfig.output.publicPath, + quiet: true +}) + +var hotMiddleware = require('webpack-hot-middleware')(compiler, { + log: () => { + } +}) +// force page reload when html-webpack-plugin template changes +compiler.plugin('compilation', function (compilation) { + compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { + hotMiddleware.publish({action: 'reload'}) + cb() + }) +}) + +// proxy api requests +Object.keys(proxyTable).forEach(function (context) { + var options = proxyTable[context] + if (typeof options === 'string') { + options = {target: options} + } + app.use(proxyMiddleware(options.filter || context, options)) +}) + +// handle fallback for HTML5 history API +app.use(require('connect-history-api-fallback')()) + +// serve webpack bundle output +app.use(devMiddleware) + +// enable hot-reload and state-preserving +// compilation error display +app.use(hotMiddleware) + +// serve pure static assets +var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) +app.use(staticPath, express.static('./static')) + +var uri = 'http://localhost:' + port + +var _resolve +var readyPromise = new Promise(resolve => { + _resolve = resolve +}) + +console.log('> Starting dev server...') +devMiddleware.waitUntilValid(() => { + console.log('> Listening at ' + uri + '\n') + // when env is testing, don't need open it + if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { + opn(uri) + } + _resolve() +}) + +var server = app.listen(port) + +module.exports = { + ready: readyPromise, + close: () => { + server.close() + } +} diff --git a/build/utils.js b/build/utils.js new file mode 100644 index 0000000..e018a2f --- /dev/null +++ b/build/utils.js @@ -0,0 +1,74 @@ +var path = require('path') +var config = require('../config') +var ExtractTextPlugin = require('extract-text-webpack-plugin') + +exports.assetsPath = function (_path) { + var assetsSubDirectory = process.env.NODE_ENV === 'production' + ? config.build.assetsSubDirectory + : config.dev.assetsSubDirectory + return path.posix.join(assetsSubDirectory, _path) +} + +exports.cssLoaders = function (options) { + options = options || {} + + var cssLoader = { + loader: 'css-loader', + options: { + minimize: process.env.NODE_ENV === 'production', + sourceMap: options.sourceMap, + // globalVars: { + // museUiTheme: `'${museUiThemePath}'`, + // } + } + } + + // generate loader string to be used with extract text plugin + function generateLoaders (loader, loaderOptions) { + var loaders = [cssLoader] + if (loader) { + loaders.push({ + loader: loader + '-loader', + options: Object.assign({}, loaderOptions, { + sourceMap: options.sourceMap + }) + }) + } + + // Extract CSS when that option is specified + // (which is the case during production build) + if (options.extract) { + return ExtractTextPlugin.extract({ + use: loaders, + fallback: 'vue-style-loader' + }) + } else { + return ['vue-style-loader'].concat(loaders) + } + } + + // https://vue-loader.vuejs.org/en/configurations/extract-css.html + return { + css: generateLoaders(), + postcss: generateLoaders(), + less: generateLoaders('less'), + sass: generateLoaders('sass', { indentedSyntax: true }), + scss: generateLoaders('sass'), + stylus: generateLoaders('stylus'), + styl: generateLoaders('stylus') + } +} + +// Generate loaders for standalone style files (outside of .vue) +exports.styleLoaders = function (options) { + var output = [] + var loaders = exports.cssLoaders(options) + for (var extension in loaders) { + var loader = loaders[extension] + output.push({ + test: new RegExp('\\.' + extension + '$'), + use: loader + }) + } + return output +} diff --git a/build/vue-loader.conf.js b/build/vue-loader.conf.js new file mode 100644 index 0000000..7aee79b --- /dev/null +++ b/build/vue-loader.conf.js @@ -0,0 +1,12 @@ +var utils = require('./utils') +var config = require('../config') +var isProduction = process.env.NODE_ENV === 'production' + +module.exports = { + loaders: utils.cssLoaders({ + sourceMap: isProduction + ? config.build.productionSourceMap + : config.dev.cssSourceMap, + extract: isProduction + }) +} diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js new file mode 100644 index 0000000..7169681 --- /dev/null +++ b/build/webpack.base.conf.js @@ -0,0 +1,74 @@ +var path = require('path') +var utils = require('./utils') +var config = require('../config') +var vueLoaderConfig = require('./vue-loader.conf') + +const museUiThemePath = path.join( + __dirname, + 'node_modules', + 'muse-ui', + 'src/styles/themes/variables/default.less' +) + +function resolve(dir) { + return path.join(__dirname, '..', dir) +} + +module.exports = { + entry: { + app: './src/main.js' + }, + output: { + path: config.build.assetsRoot, + filename: '[name].js', + publicPath: process.env.NODE_ENV === 'production' + ? config.build.assetsPublicPath + : config.dev.assetsPublicPath + }, + resolve: { + extensions: ['.js', '.vue', '.json', '.scss'], + alias: { + 'muse-components': 'muse-ui/src', + 'vue$': 'vue/dist/vue.esm.js', + '@': resolve('src') + } + }, + module: { + rules: [ + { + test: /muse-ui.src.*?js$/, + loader: 'babel-loader' + }, + { + test: /\.vue$/, + loader: 'vue-loader', + options: vueLoaderConfig + }, + { + test: /\.js$/, + loader: 'babel-loader', + include: [resolve('src'), resolve('test')] + }, + { + test: /\.scss$/, + loaders: 'style-loader!css-loader!sass-loader' + }, + { + test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('img/[name].[hash:7].[ext]') + } + }, + { + test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('fonts/[name].[hash:7].[ext]') + } + } + ] + } +} diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js new file mode 100644 index 0000000..5470402 --- /dev/null +++ b/build/webpack.dev.conf.js @@ -0,0 +1,35 @@ +var utils = require('./utils') +var webpack = require('webpack') +var config = require('../config') +var merge = require('webpack-merge') +var baseWebpackConfig = require('./webpack.base.conf') +var HtmlWebpackPlugin = require('html-webpack-plugin') +var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') + +// add hot-reload related code to entry chunks +Object.keys(baseWebpackConfig.entry).forEach(function (name) { + baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) +}) + +module.exports = merge(baseWebpackConfig, { + module: { + rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) + }, + // cheap-module-eval-source-map is faster for development + devtool: '#cheap-module-eval-source-map', + plugins: [ + new webpack.DefinePlugin({ + 'process.env': config.dev.env + }), + // https://github.com/glenjamin/webpack-hot-middleware#installation--usage + new webpack.HotModuleReplacementPlugin(), + new webpack.NoEmitOnErrorsPlugin(), + // https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', + template: 'index.html', + inject: true + }), + new FriendlyErrorsPlugin() + ] +}) diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js new file mode 100644 index 0000000..99713cc --- /dev/null +++ b/build/webpack.prod.conf.js @@ -0,0 +1,124 @@ +var path = require('path') +var utils = require('./utils') +var webpack = require('webpack') +var config = require('../config') +var merge = require('webpack-merge') +var baseWebpackConfig = require('./webpack.base.conf') +var CopyWebpackPlugin = require('copy-webpack-plugin') +var HtmlWebpackPlugin = require('html-webpack-plugin') +var ExtractTextPlugin = require('extract-text-webpack-plugin') +var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') + +var env = process.env.NODE_ENV === 'testing' + ? require('../config/test.env') + : config.build.env + +var webpackConfig = merge(baseWebpackConfig, { + module: { + rules: utils.styleLoaders({ + sourceMap: config.build.productionSourceMap, + extract: true + }) + }, + devtool: config.build.productionSourceMap ? '#source-map' : false, + output: { + path: config.build.assetsRoot, + filename: utils.assetsPath('js/[name].[chunkhash].js'), + chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') + }, + plugins: [ + // http://vuejs.github.io/vue-loader/en/workflow/production.html + new webpack.DefinePlugin({ + 'process.env': env + }), + new webpack.optimize.UglifyJsPlugin({ + compress: { + warnings: false + }, + sourceMap: true + }), + // extract css into its own file + new ExtractTextPlugin({ + filename: utils.assetsPath('css/[name].[contenthash].css') + }), + // Compress extracted CSS. We are using this plugin so that possible + // duplicated CSS from different components can be deduped. + new OptimizeCSSPlugin({ + cssProcessorOptions: { + safe: true + } + }), + // generate dist index.html with correct asset hash for caching. + // you can customize output by editing /index.html + // see https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: process.env.NODE_ENV === 'testing' + ? 'index.html' + : config.build.index, + template: 'index.html', + inject: true, + minify: { + removeComments: true, + collapseWhitespace: true, + removeAttributeQuotes: true + // more options: + // https://github.com/kangax/html-minifier#options-quick-reference + }, + // necessary to consistently work with multiple chunks via CommonsChunkPlugin + chunksSortMode: 'dependency' + }), + // split vendor js into its own file + new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + minChunks: function (module, count) { + // any required modules inside node_modules are extracted to vendor + return ( + module.resource && + /\.js$/.test(module.resource) && + module.resource.indexOf( + path.join(__dirname, '../node_modules') + ) === 0 + ) + } + }), + // extract webpack runtime and module manifest to its own file in order to + // prevent vendor hash from being updated whenever app bundle is updated + new webpack.optimize.CommonsChunkPlugin({ + name: 'manifest', + chunks: ['vendor'] + }), + // copy custom static assets + new CopyWebpackPlugin([ + { + from: path.resolve(__dirname, '../static'), + to: config.build.assetsSubDirectory, + ignore: ['.*'] + } + ]) + ] +}) + +if (config.build.productionGzip) { + var CompressionWebpackPlugin = require('compression-webpack-plugin') + + webpackConfig.plugins.push( + new CompressionWebpackPlugin({ + asset: '[path].gz[query]', + algorithm: 'gzip', + test: new RegExp( + '\\.(' + + config.build.productionGzipExtensions.join('|') + + ')$' + ), + threshold: 10240, + minRatio: 0.8 + }) + ) +} + +if (config.build.bundleAnalyzerReport) { + var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin + webpackConfig.plugins.push(new BundleAnalyzerPlugin()) +} + +module.exports = webpackConfig diff --git a/build/webpack.test.conf.js b/build/webpack.test.conf.js new file mode 100644 index 0000000..d6c8c8d --- /dev/null +++ b/build/webpack.test.conf.js @@ -0,0 +1,31 @@ +// This is the webpack config used for unit tests. + +var utils = require('./utils') +var webpack = require('webpack') +var merge = require('webpack-merge') +var baseConfig = require('./webpack.base.conf') + +var webpackConfig = merge(baseConfig, { + // use inline sourcemap for karma-sourcemap-loader + module: { + rules: utils.styleLoaders() + }, + devtool: '#inline-source-map', + resolveLoader: { + alias: { + // necessary to to make lang="scss" work in test when using vue-loader's ?inject option + // see discussion at https://github.com/vuejs/vue-loader/issues/724 + 'scss-loader': 'sass-loader' + } + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env': require('../config/test.env') + }) + ] +}) + +// no need for app entry during tests +delete webpackConfig.entry + +module.exports = webpackConfig diff --git a/config/dev.env.js b/config/dev.env.js new file mode 100644 index 0000000..efead7c --- /dev/null +++ b/config/dev.env.js @@ -0,0 +1,6 @@ +var merge = require('webpack-merge') +var prodEnv = require('./prod.env') + +module.exports = merge(prodEnv, { + NODE_ENV: '"development"' +}) diff --git a/config/index.js b/config/index.js new file mode 100644 index 0000000..6068c3d --- /dev/null +++ b/config/index.js @@ -0,0 +1,38 @@ +// see http://vuejs-templates.github.io/webpack for documentation. +var path = require('path') + +module.exports = { + build: { + env: require('./prod.env'), + index: path.resolve(__dirname, '../dist/index.html'), + assetsRoot: path.resolve(__dirname, '../dist'), + assetsSubDirectory: 'static', + assetsPublicPath: './', + productionSourceMap: false, + // Gzip off by default as many popular static hosts such as + // Surge or Netlify already gzip all static assets for you. + // Before setting to `true`, make sure to: + // npm install --save-dev compression-webpack-plugin + productionGzip: false, + productionGzipExtensions: ['js', 'css'], + // Run the build command with an extra argument to + // View the bundle analyzer report after build finishes: + // `npm run build --report` + // Set to `true` or `false` to always turn it on or off + bundleAnalyzerReport: process.env.npm_config_report + }, + dev: { + env: require('./dev.env'), + port: 8888, + autoOpenBrowser: true, + assetsSubDirectory: 'static', + assetsPublicPath: '/', + proxyTable: {}, + // CSS Sourcemaps off by default because relative paths are "buggy" + // with this option, according to the CSS-Loader README + // (https://github.com/webpack/css-loader#sourcemaps) + // In our experience, they generally work as expected, + // just be aware of this issue when enabling this option. + cssSourceMap: false + } +} diff --git a/config/prod.env.js b/config/prod.env.js new file mode 100644 index 0000000..773d263 --- /dev/null +++ b/config/prod.env.js @@ -0,0 +1,3 @@ +module.exports = { + NODE_ENV: '"production"' +} diff --git a/config/test.env.js b/config/test.env.js new file mode 100644 index 0000000..89f90de --- /dev/null +++ b/config/test.env.js @@ -0,0 +1,6 @@ +var merge = require('webpack-merge') +var devEnv = require('./dev.env') + +module.exports = merge(devEnv, { + NODE_ENV: '"testing"' +}) diff --git a/index.html b/index.html new file mode 100644 index 0000000..da6de6c --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + + Vue+MuseUI+Node + + + + + + + + + + + + + + +
+ + + diff --git a/mockdata.json b/mockdata.json new file mode 100644 index 0000000..1ac678b --- /dev/null +++ b/mockdata.json @@ -0,0 +1,76 @@ +{ + "self": { + "_id": 0, + "avatar": "/static/images/sugars.jpeg", + "birthday": "28/3/94", + "name": "Sugars苏哥", + "gender": "male", + "email": "sugars.su@celnet.com.cn", + "phone": "13823695205", + "address": "广东省深圳市福田区中国凤凰大厦2栋601", + "explain": "Ut excepteur consequat duis adipisicing ad elit.", + "about": "Esse esse pariatur sint irure fugiat anim anim magna irure qui. Sit nostrud veniam laboris ipsum voluptate ipsum dolore esse eiusmod sunt duis commodo ad. Dolor commodo Lorem proident excepteur quis magna proident duis adipisicing ea anim veniam aliquip ullamco. Ipsum fugiat Lorem id duis consequat aliquip proident aliquip in duis adipisicing. Qui ad est qui laborum reprehenderit occaecat nisi ad qui ullamco non.\r\n" + }, + "friend": [ + { + "_id": 1, + "avatar": "/static/images/avatar1.jpg", + "birthday": "11/3/95", + "name": "Pate Tran", + "gender": "female", + "email": "patetran@genekom.com", + "phone": "178412313", + "address": "North Carolina, 2584", + "explain": "Dolore ea cupidatat deserunt nostrud officia est ", + "about": "Ullamco amet velit esse et duis deserunt ex in non eiusmod. Amet anim nisi aliqua minim aute consectetur amet fugiat labore in deserunt duis sunt. Deserunt tempor dolor in adipisicing irure magna in dolor magna exercitation Lorem et Lorem velit. Elit quis et proident ad aliquip qui aute anim eu minim deserunt excepteur. Mollit commodo sunt esse aute do. Quis non nisi id incididunt exercitation sint exercitation.\r\n" + }, + { + "_id": 2, + "avatar": "/static/images/avatar2.jpg", + "birthday": "21/6/97", + "name": "Cardenas Burnett", + "gender": "female", + "email": "cardenasburnett@genekom.com", + "phone": "135523432", + "address": "Salix, Delaware, 442", + "explain": "Ipsum sit cupidatat dolore voluptate.", + "about": "Ea laboris magna ex nisi Lorem anim sint excepteur. Amet nostrud proident dolore ad enim amet ipsum deserunt fugiat duis nisi tempor. Ex id amet duis do nostrud deserunt fugiat. Velit reprehenderit nulla exercitation minim pariatur deserunt do nulla enim Lorem laboris nisi labore incididunt. Laboris proident aliquip in proident cupidatat eiusmod sint nisi sit. Mollit qui pariatur ut exercitation mollit cupidatat. Ad ex cillum voluptate consequat laboris cupidatat.\r\n" + }, + { + "_id": 3, + "avatar": "/static/images/avatar3.jpg", + "birthday": "3/3/99", + "name": "Imelda Horne", + "gender": "female", + "email": "imeldahorne@genekom.com", + "phone": "1581234219", + "address": "Coldiron, Palau, 7757", + "explain": "Voluptate ad fugiat magna ut cupidatat", + "about": "Irure consectetur do ea ea ex proident laborum ex dolore incididunt mollit fugiat. Lorem minim adipisicing labore cillum velit adipisicing. Laboris laboris proident esse deserunt enim exercitation eu qui pariatur excepteur ad Lorem enim veniam.\r\n" + }, + { + "_id": 4, + "avatar": "/static/images/sugars.jpeg", + "birthday": "3/3/90", + "name": "Mars", + "gender": "female", + "email": "asd@genekom.com", + "phone": "13888888888", + "address": "HK", + "explain": "HAHAHAHA", + "about": "LALALA" + }, + { + "_id": 5, + "avatar": "/static/images/sugars.jpeg", + "birthday": "3/3/90", + "name": "Mars", + "gender": "female", + "email": "asd@genekom.com", + "phone": "13888888888", + "address": "HK", + "explain": "HAHAHAHA", + "about": "LALALA" + } + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..71062b3 --- /dev/null +++ b/package.json @@ -0,0 +1,97 @@ +{ + "name": "vue2-vuex", + "version": "1.0.0", + "description": "A Vue.js project", + "author": "sugars", + "private": true, + "scripts": { + "dev": "node build/dev-server.js", + "start": "node build/dev-server.js", + "build": "node build/build.js", + "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", + "e2e": "node test/e2e/runner.js", + "test": "npm run unit && npm run e2e" + }, + "dependencies": { + "axios": "^0.16.2", + "fastclick": "^1.0.6", + "muse-ui": "^2.0.3", + "superagent": "^3.5.2", + "vue": "^2.2.6", + "vue-lazyload": "^1.0.6", + "vue-router": "^2.3.1", + "vuex": "^2.3.1" + }, + "devDependencies": { + "autoprefixer": "^6.7.2", + "babel-core": "^6.22.1", + "babel-loader": "^6.2.10", + "babel-plugin-transform-runtime": "^6.22.0", + "babel-preset-env": "^1.3.2", + "babel-preset-stage-2": "^6.22.0", + "babel-register": "^6.22.0", + "chalk": "^1.1.3", + "connect-history-api-fallback": "^1.3.0", + "copy-webpack-plugin": "^4.0.1", + "css-loader": "^0.28.0", + "eventsource-polyfill": "^0.9.6", + "express": "^4.14.1", + "extract-text-webpack-plugin": "^2.0.0", + "file-loader": "^0.11.1", + "friendly-errors-webpack-plugin": "^1.1.3", + "html-webpack-plugin": "^2.28.0", + "http-proxy-middleware": "^0.17.3", + "keycode": "^2.1.8", + "webpack-bundle-analyzer": "^2.2.1", + "cross-env": "^4.0.0", + "karma": "^1.4.1", + "karma-coverage": "^1.1.1", + "karma-mocha": "^1.3.0", + "karma-phantomjs-launcher": "^1.0.2", + "karma-phantomjs-shim": "^1.4.0", + "karma-sinon-chai": "^1.3.1", + "karma-sourcemap-loader": "^0.3.7", + "karma-spec-reporter": "0.0.30", + "karma-webpack": "^2.0.2", + "lolex": "^1.5.2", + "mocha": "^3.2.0", + "chai": "^3.5.0", + "sinon": "^2.1.0", + "sinon-chai": "^2.8.0", + "superagent": "^3.5.2", + "inject-loader": "^3.0.0", + "babel-plugin-istanbul": "^4.1.1", + "phantomjs-prebuilt": "^2.1.14", + "chromedriver": "^2.27.2", + "cross-spawn": "^5.0.1", + "nightwatch": "^0.9.12", + "selenium-server": "^3.0.1", + "semver": "^5.3.0", + "shelljs": "^0.7.6", + "opn": "^4.0.2", + "optimize-css-assets-webpack-plugin": "^1.3.0", + "ora": "^1.2.0", + "rimraf": "^2.6.0", + "url-loader": "^0.5.8", + "node-sass": "^4.5.2", + "sass-loader": "^6.0.5", + "less": "^2.7.2", + "less-loader": "^4.0.3", + "vue-loader": "^11.3.4", + "vue-style-loader": "^2.0.5", + "vue-template-compiler": "^2.2.6", + "webpack": "^2.3.3", + "webpack-dev-middleware": "^1.10.0", + "webpack-hot-middleware": "^2.18.0", + "webpack-merge": "^4.1.0" + }, + "engines": { + "node": ">= 4.0.0", + "npm": ">= 3.0.0" + }, + "browserslist": [ + "> 1%", + "last 2 versions", + "not ie <= 8" + ] +} diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..5ad6c80 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..f3d2503 Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/common/css/base.css b/src/common/css/base.css new file mode 100644 index 0000000..db4a3ee --- /dev/null +++ b/src/common/css/base.css @@ -0,0 +1,21 @@ +html, body { + font-weight: 200; + font-family: 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light'; + background-color: #f4f4f6; +} + +.color-black { + color: #000; +} + +.color-lightblack { + color: rgba(0, 0, 0, .54); +} + +.color-white { + color: #fff; +} + +.color-active { + color: #2e2c6b; +} diff --git a/src/components/bottomtab/bottom-tab.vue b/src/components/bottomtab/bottom-tab.vue new file mode 100644 index 0000000..872a4fd --- /dev/null +++ b/src/components/bottomtab/bottom-tab.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/src/components/discover/discover.vue b/src/components/discover/discover.vue new file mode 100644 index 0000000..f2b7f23 --- /dev/null +++ b/src/components/discover/discover.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/components/friends/friends.vue b/src/components/friends/friends.vue new file mode 100644 index 0000000..f2b7f23 --- /dev/null +++ b/src/components/friends/friends.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/components/home/home.vue b/src/components/home/home.vue new file mode 100644 index 0000000..18cadcf --- /dev/null +++ b/src/components/home/home.vue @@ -0,0 +1,159 @@ + + + diff --git a/src/components/home/swipeDelete.vue b/src/components/home/swipeDelete.vue new file mode 100644 index 0000000..b13516f --- /dev/null +++ b/src/components/home/swipeDelete.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/components/topnav/top-nav.vue b/src/components/topnav/top-nav.vue new file mode 100644 index 0000000..f1b0d96 --- /dev/null +++ b/src/components/topnav/top-nav.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..5a142aa --- /dev/null +++ b/src/main.js @@ -0,0 +1,48 @@ +// The Vue build version to load with the `import` command +// (runtime-only or standalone) has been set in webpack.base.conf with an alias. +import Vue from 'vue' +import axios from 'axios' +import App from './App' +import FastClick from 'fastclick' +import router from './router' +import store from './vuex/store' +import MuseUi from './muse-ui.config' + +// museui carbon主题 +import 'muse-ui/dist/theme-carbon.css' +import './common/css/base.css' + +Vue.use(MuseUi) + +import VueLazyload from 'vue-lazyload' + +Vue.use(VueLazyload, { + preLoad: 1.3, + error: 'static/images/lazy.jpg', + loading: 'static/images/lazy.jpg', + attempt: 1, + listenEvents: ['scroll'] +}) + +FastClick.attach(document.body) + +Vue.prototype.$http = axios + +router.replace('home') + +Vue.config.productionTip = false + +/* eslint-disable no-new */ +new Vue({ + el: '#app', + router, + store, + template: '', + components: {App}, + // 组件创建前,请求用户数据 + beforeCreate() { + this.$store.dispatch('getAllData', this) + this.$store.dispatch('getSFBusinessCard', this) + } +}) + diff --git a/src/muse-ui.config.js b/src/muse-ui.config.js new file mode 100644 index 0000000..f2172a7 --- /dev/null +++ b/src/muse-ui.config.js @@ -0,0 +1,48 @@ +// 单组件加载 + +// 加载基础样式 +import 'muse-components/styles/base.less' +import appBar from 'muse-components/appBar' +import avatar from 'muse-components/avatar' +import badge from 'muse-components/badge' +import dialog from 'muse-components/dialog' +import * as bottomNav from 'muse-components/bottomNav' +import flatButton from 'muse-components/flatButton' +import iconButton from 'muse-components/iconButton' +import chip from 'muse-components/chip' +import drawer from 'muse-components/drawer' +import icon from 'muse-components/icon' +import * as list from 'muse-components/list' +import textField from 'muse-components/textField' +import * as tabs from 'muse-components/tabs' +import divider from 'muse-components/divider' +import subHeader from 'muse-components/subHeader' +// 这个模块在项目官网上并没有看到,但是看到作者在issues上回答时说是高分辨率屏的处理,于是就试着加进去了 +import {retina} from 'muse-components/utils' + +const components = { + appBar, + avatar, + badge, + dialog, + ...bottomNav, + flatButton, + iconButton, + chip, + drawer, + icon, + ...list, + textField, + ...tabs, + divider, + subHeader +} + +export default { + install(Vue) { + Object.keys(components).forEach((key) => { + Vue.component(components[key].name, components[key]) + }) + retina() + } +} diff --git a/src/router/index.js b/src/router/index.js new file mode 100644 index 0000000..80be225 --- /dev/null +++ b/src/router/index.js @@ -0,0 +1,18 @@ +import Vue from 'vue' +import Router from 'vue-router' +// 注册router组件 +Vue.use(Router) +// 导入组件 +import home from '../components/home/home.vue' +import friends from '../components/friends/friends.vue' +import discover from '../components/discover/discover.vue' + +let routes = [ + {path: '/home', name: 'home', component: home}, + {path: '/friends', name: 'friends', component: friends}, + {path: '/discover', name: 'discover', component: discover} +] + +export default new Router({ + routes +}) diff --git a/src/vuex/actions.js b/src/vuex/actions.js new file mode 100644 index 0000000..918ac52 --- /dev/null +++ b/src/vuex/actions.js @@ -0,0 +1,41 @@ +/** + * Created by Sugar on 2017/5/17. + */ + +// actions里存放的是异步操作 +// 由于vuex中的state的变更只能由mutations进行操作,所以actions不直接进行数据操作,而是调用mutations方法 +// 以下出现的that都是vue实例对象,因为把axios绑定在了Vue原型上,vuex无法调用,所以这里需要传入this +const actions = { + // 异步获取基础数据 + // 这里使用了es7的async函数,相当于封装了promis的generator + getAllData: async ({commit}, that) => { + let self = {} + let friends = {} + + await that.$http.get('/api/self').then(({data}) => { + self = data.data + }) + + await that.$http.get('/api/friends').then(({data}) => { + friends = data.data + }) + + commit('getData', {self, friends}) + }, + // 测试请求SF接口 + getSFBusinessCard: async ({commit}, that) => { + let bc = {}; + + await that.$http.get('/api/getBusinessCard').then(({data}) => { + bc = JSON.parse(data.data); + }) + + console.log('BusinessCard', bc); + + let records = bc.records || []; + commit('getBCData', {records}) + } +} + + +export default actions diff --git a/src/vuex/getters.js b/src/vuex/getters.js new file mode 100644 index 0000000..a83be0a --- /dev/null +++ b/src/vuex/getters.js @@ -0,0 +1,22 @@ +/** + * Created by Sugar on 2017/5/17. + */ + +const getters = { + // 对当前队列消息列表进行加工,添加对应好友资料 + nowMessageList: (state) => { + let list = state.messageList; + + list.forEach(x => { + // 筛选_id相同的好友 + let friend = state.data.friends.filter(i => i._id === x._id)[0] + if (x._id !== 5) { + x.friend = friend + } + }) + + return list + } +} + +export default getters diff --git a/src/vuex/mutations.js b/src/vuex/mutations.js new file mode 100644 index 0000000..1e4ba15 --- /dev/null +++ b/src/vuex/mutations.js @@ -0,0 +1,39 @@ +/** + * Created by Sugar on 2017/5/17. + */ + +const mutations = { + // ajax获取用户信息 + getData: (state, data) => { + // 将ajax获取到的值赋予state + state.data = data + // ajax请求状态为结束 + state.isAjax = false + }, + // 页面标题变更 + changeTitle: (state, {title}) => { + state.headerTitle = title + }, + // 删除消息队列中的消息 + removeMessage: (state, {_id}) => { + state.messageList.forEach((item, index, arr) => { + if (item._id === _id) { + arr.splice(index, 1) + } + }) + }, + + // 测试获取sf名片数据 + getBCData: (state, {records}) => { + let list = [{ + _id: 1, + list: [{_id: 1, message: '你可以和我聊天', time: '4:28'}] + }]; + records.forEach((item, index, arr) => { + list.push({_id: 5, friend: {_id: 5, name: item.Name}, list: [{_id: 1, message: item.Company__c, item: '12:28'}]}) + }) + state.messageList = state.messageList.concat(list) + } +} + +export default mutations diff --git a/src/vuex/store.js b/src/vuex/store.js new file mode 100644 index 0000000..a28b6f6 --- /dev/null +++ b/src/vuex/store.js @@ -0,0 +1,73 @@ +/** + * Created by Sugar on 2017/5/17. + */ +import Vue from 'vue' +import Vuex from 'vuex' +import mutations from './mutations' +import getters from './getters' +import actions from './actions' + +// 注册vuex +Vue.use(Vuex) + +// 初始化一些常用数据,根据vue的理念,使用到的数据都必须先进行初始化设置。 +let state = { + // 对话框 + dialog: false, + // 侧边栏 + sidebar: { + open: false, + docked: true + }, + // 用户主页 + personindex: false, + // 搜索框 + search: false, + // 导航栏标题 + headerTitle: "Message", + // ajax请求数据是否结束 + isAjax: true, + // 当前被选中或者在聊天中的friend的_id + activeId: 0, + // 初始化基础数据 + data: {self: {}, friends: []}, + // 聊天队列,这里为每个朋友添加了一个聊天队列,偷懒写法,如果有需要可以改成动态添加 + // _id是作为聊天队列的标记,list是聊天内容,list里的数据格式{_id:xx, message:xxx},组件内会根据_id来将对话插入 + // 到左边,还是右边,判断message是自己还是ai发出的 + messageList: [ + { + _id: 1, + list: [{_id: 1, message: '你可以和我聊天', time: '4:28'}] + }, { + _id: 2, + list: [{_id: 2, message: '我会讲笑话哦', time: '9:50'}] + }, { + _id: 3, + list: [{_id: 3, message: '要买点吃的吗', time: '3:12'}] + }, { + _id: 4, + list: [{_id: 4, message: '哈哈哈哈哈哈嗝', time: '3:12'}] + } + ], + // 消息队列副本,由于没有数据库,所以采用这样折中的方法 + messageListFB: [ + { + _id: 1, + list: [{_id: 1, message: '你可以和我聊天', time: '4:28'}] + }, { + _id: 2, + list: [{_id: 2, message: '我会讲笑话哦', time: '9:50'}] + }, { + _id: 3, + list: [{_id: 3, message: '请问你要来点兔子吗', time: '3:12'}] + } + ] +} + +// 导出一个新生成的Store对象 +export default new Vuex.Store({ + state, + mutations, + actions, + getters +}) diff --git a/static/.gitkeep b/static/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/static/css/css.css b/static/css/css.css new file mode 100644 index 0000000..eab549e --- /dev/null +++ b/static/css/css.css @@ -0,0 +1,30 @@ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'), url(http://fonts.gstatic.com/s/roboto/v16/Hgo13k-tfSpn0qi1SFdUfbO3LdcAZYWl9Si6vvxL-qU.woff) format('woff'); +} +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), url(http://fonts.gstatic.com/s/roboto/v16/CrYjSnGjrRCn0pd9VQsnFOvvDin1pK8aKteLpeZ5c0A.woff) format('woff'); +} +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 500; + src: local('Roboto Medium'), local('Roboto-Medium'), url(http://fonts.gstatic.com/s/roboto/v16/RxZJdnzeo3R5zSexge8UUbO3LdcAZYWl9Si6vvxL-qU.woff) format('woff'); +} +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 700; + src: local('Roboto Bold'), local('Roboto-Bold'), url(http://fonts.gstatic.com/s/roboto/v16/d-6IYplOFocCacKzxwXSOLO3LdcAZYWl9Si6vvxL-qU.woff) format('woff'); +} +@font-face { + font-family: 'Roboto'; + font-style: italic; + font-weight: 400; + src: local('Roboto Italic'), local('Roboto-Italic'), url(http://fonts.gstatic.com/s/roboto/v16/1pO9eUAp8pSF8VnRTP3xnnYhjbSpvc47ee6xR_80Hnw.woff) format('woff'); +} diff --git a/static/css/icon.css b/static/css/icon.css new file mode 100644 index 0000000..3e15388 --- /dev/null +++ b/static/css/icon.css @@ -0,0 +1,22 @@ +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url(http://fonts.gstatic.com/s/materialicons/v28/2fcrYFNaTjcS6g4U3t-Y5RV6cRhDpPC5P4GCEJpqGoc.woff) format('woff'); +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; +} diff --git a/static/css/reset.css b/static/css/reset.css new file mode 100644 index 0000000..cff2761 --- /dev/null +++ b/static/css/reset.css @@ -0,0 +1,90 @@ +/** + * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) + * http://cssreset.com + */ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, +menu, nav, output, ruby, section, summary, +time, mark, audio, video, input { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font-weight: normal; + vertical-align: baseline; +} + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, menu, nav, section { + display: block; +} + +body { + line-height: 1; +} + +blockquote, q { + quotes: none; +} + +blockquote:before, blockquote:after, +q:before, q:after { + content: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +/* custom */ +a { + color: #7e8c8d; + text-decoration: none; + -webkit-backface-visibility: hidden; +} + +li { + list-style: none; +} + +::-webkit-scrollbar { + width: 5px; + height: 5px; +} + +::-webkit-scrollbar-track-piece { + background-color: rgba(0, 0, 0, 0.2); + -webkit-border-radius: 6px; +} + +::-webkit-scrollbar-thumb:vertical { + height: 5px; + background-color: rgba(125, 125, 125, 0.7); + -webkit-border-radius: 6px; +} + +::-webkit-scrollbar-thumb:horizontal { + width: 5px; + background-color: rgba(125, 125, 125, 0.7); + -webkit-border-radius: 6px; +} + +html, body { + width: 100%; +} + +body { + -webkit-text-size-adjust: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} \ No newline at end of file diff --git a/static/images/avatar.jpg b/static/images/avatar.jpg new file mode 100644 index 0000000..3cbd276 Binary files /dev/null and b/static/images/avatar.jpg differ diff --git a/static/images/avatar1.jpg b/static/images/avatar1.jpg new file mode 100644 index 0000000..b81444a Binary files /dev/null and b/static/images/avatar1.jpg differ diff --git a/static/images/avatar2.jpg b/static/images/avatar2.jpg new file mode 100644 index 0000000..d8f80e4 Binary files /dev/null and b/static/images/avatar2.jpg differ diff --git a/static/images/avatar3.jpg b/static/images/avatar3.jpg new file mode 100644 index 0000000..f4b3beb Binary files /dev/null and b/static/images/avatar3.jpg differ diff --git a/static/images/lazy.jpg b/static/images/lazy.jpg new file mode 100644 index 0000000..6a692cb Binary files /dev/null and b/static/images/lazy.jpg differ diff --git a/static/images/sugars.jpeg b/static/images/sugars.jpeg new file mode 100644 index 0000000..a678fb6 Binary files /dev/null and b/static/images/sugars.jpeg differ diff --git a/test/e2e/custom-assertions/elementCount.js b/test/e2e/custom-assertions/elementCount.js new file mode 100644 index 0000000..c0d5fe0 --- /dev/null +++ b/test/e2e/custom-assertions/elementCount.js @@ -0,0 +1,26 @@ +// A custom Nightwatch assertion. +// the name of the method is the filename. +// can be used in tests like this: +// +// browser.assert.elementCount(selector, count) +// +// for how to write custom assertions see +// http://nightwatchjs.org/guide#writing-custom-assertions +exports.assertion = function (selector, count) { + this.message = 'Testing if element <' + selector + '> has count: ' + count + this.expected = count + this.pass = function (val) { + return val === this.expected + } + this.value = function (res) { + return res.value + } + this.command = function (cb) { + var self = this + return this.api.execute(function (selector) { + return document.querySelectorAll(selector).length + }, [selector], function (res) { + cb.call(self, res) + }) + } +} diff --git a/test/e2e/nightwatch.conf.js b/test/e2e/nightwatch.conf.js new file mode 100644 index 0000000..f019c0a --- /dev/null +++ b/test/e2e/nightwatch.conf.js @@ -0,0 +1,46 @@ +require('babel-register') +var config = require('../../config') + +// http://nightwatchjs.org/gettingstarted#settings-file +module.exports = { + src_folders: ['test/e2e/specs'], + output_folder: 'test/e2e/reports', + custom_assertions_path: ['test/e2e/custom-assertions'], + + selenium: { + start_process: true, + server_path: require('selenium-server').path, + host: '127.0.0.1', + port: 4444, + cli_args: { + 'webdriver.chrome.driver': require('chromedriver').path + } + }, + + test_settings: { + default: { + selenium_port: 4444, + selenium_host: 'localhost', + silent: true, + globals: { + devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) + } + }, + + chrome: { + desiredCapabilities: { + browserName: 'chrome', + javascriptEnabled: true, + acceptSslCerts: true + } + }, + + firefox: { + desiredCapabilities: { + browserName: 'firefox', + javascriptEnabled: true, + acceptSslCerts: true + } + } + } +} diff --git a/test/e2e/runner.js b/test/e2e/runner.js new file mode 100644 index 0000000..85d67d6 --- /dev/null +++ b/test/e2e/runner.js @@ -0,0 +1,33 @@ +// 1. start the dev server using production config +process.env.NODE_ENV = 'testing' +var server = require('../../build/dev-server.js') + +server.ready.then(() => { + // 2. run the nightwatch test suite against it + // to run in additional browsers: + // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" + // 2. add it to the --env flag below + // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` + // For more information on Nightwatch's config file, see + // http://nightwatchjs.org/guide#settings-file + var opts = process.argv.slice(2) + if (opts.indexOf('--config') === -1) { + opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) + } + if (opts.indexOf('--env') === -1) { + opts = opts.concat(['--env', 'chrome']) + } + + var spawn = require('cross-spawn') + var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) + + runner.on('exit', function (code) { + server.close() + process.exit(code) + }) + + runner.on('error', function (err) { + server.close() + throw err + }) +}) diff --git a/test/e2e/specs/test.js b/test/e2e/specs/test.js new file mode 100644 index 0000000..a7b1bd9 --- /dev/null +++ b/test/e2e/specs/test.js @@ -0,0 +1,19 @@ +// For authoring Nightwatch tests, see +// http://nightwatchjs.org/guide#usage + +module.exports = { + 'default e2e tests': function (browser) { + // automatically uses dev Server port from /config.index.js + // default: http://localhost:8080 + // see nightwatch.conf.js + const devServer = browser.globals.devServerURL + + browser + .url(devServer) + .waitForElementVisible('#app', 5000) + .assert.elementPresent('.hello') + .assert.containsText('h1', 'Welcome to Your Vue.js App') + .assert.elementCount('img', 1) + .end() + } +} diff --git a/test/unit/.eslintrc b/test/unit/.eslintrc new file mode 100644 index 0000000..959a4f4 --- /dev/null +++ b/test/unit/.eslintrc @@ -0,0 +1,9 @@ +{ + "env": { + "mocha": true + }, + "globals": { + "expect": true, + "sinon": true + } +} diff --git a/test/unit/index.js b/test/unit/index.js new file mode 100644 index 0000000..c69f33f --- /dev/null +++ b/test/unit/index.js @@ -0,0 +1,13 @@ +import Vue from 'vue' + +Vue.config.productionTip = false + +// require all test files (files that ends with .spec.js) +const testsContext = require.context('./specs', true, /\.spec$/) +testsContext.keys().forEach(testsContext) + +// require all src files except main.js for coverage. +// you can also change this to match only the subset of files that +// you want coverage for. +const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/) +srcContext.keys().forEach(srcContext) diff --git a/test/unit/karma.conf.js b/test/unit/karma.conf.js new file mode 100644 index 0000000..8e4951c --- /dev/null +++ b/test/unit/karma.conf.js @@ -0,0 +1,33 @@ +// This is a karma config file. For more details see +// http://karma-runner.github.io/0.13/config/configuration-file.html +// we are also using it with karma-webpack +// https://github.com/webpack/karma-webpack + +var webpackConfig = require('../../build/webpack.test.conf') + +module.exports = function (config) { + config.set({ + // to run in additional browsers: + // 1. install corresponding karma launcher + // http://karma-runner.github.io/0.13/config/browsers.html + // 2. add it to the `browsers` array below. + browsers: ['PhantomJS'], + frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'], + reporters: ['spec', 'coverage'], + files: ['./index.js'], + preprocessors: { + './index.js': ['webpack', 'sourcemap'] + }, + webpack: webpackConfig, + webpackMiddleware: { + noInfo: true + }, + coverageReporter: { + dir: './coverage', + reporters: [ + { type: 'lcov', subdir: '.' }, + { type: 'text-summary' } + ] + } + }) +} diff --git a/test/unit/specs/Hello.spec.js b/test/unit/specs/Hello.spec.js new file mode 100644 index 0000000..80140ba --- /dev/null +++ b/test/unit/specs/Hello.spec.js @@ -0,0 +1,11 @@ +import Vue from 'vue' +import Hello from '@/components/Hello' + +describe('Hello.vue', () => { + it('should render correct contents', () => { + const Constructor = Vue.extend(Hello) + const vm = new Constructor().$mount() + expect(vm.$el.querySelector('.hello h1').textContent) + .to.equal('Welcome to Your Vue.js App') + }) +})