├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .npmrc
├── .nvmrc
├── .postcssrc.js
├── .travis.yml
├── README.md
├── build
├── build.js
├── check-versions.js
├── dev-client.js
├── dev-server.js
├── load-minified.js
├── service-worker-dev.js
├── service-worker-prod.js
├── utils.js
├── vue-loader.conf.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
├── webpack.prod.conf.js
└── webpack.test.conf.js
├── config
├── dev.env.js
├── index.js
├── prod.env.js
└── test.env.js
├── index.html
├── package-lock.json
├── package.json
├── src
├── App.vue
├── assets
│ ├── images
│ │ └── logo.png
│ └── styles
│ │ ├── _animations.less
│ │ ├── _variables.less
│ │ └── app.less
├── components
│ ├── Counter.vue
│ ├── Hello.vue
│ ├── Router.vue
│ ├── RouterChild.vue
│ └── Store.vue
├── main.js
├── router
│ └── index.js
└── store
│ ├── createState.js
│ ├── index.js
│ ├── mutations.js
│ └── persistedState.js
├── static
├── img
│ └── icons
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon-120x120.png
│ │ ├── apple-touch-icon-152x152.png
│ │ ├── apple-touch-icon-180x180.png
│ │ ├── apple-touch-icon-60x60.png
│ │ ├── apple-touch-icon-76x76.png
│ │ ├── apple-touch-icon.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon.ico
│ │ ├── msapplication-icon-144x144.png
│ │ ├── mstile-150x150.png
│ │ └── safari-pinned-tab.svg
└── manifest.json
└── test
├── e2e
├── custom-assertions
│ └── elementCount.js
├── nightwatch.conf.js
├── runner.js
└── specs
│ └── test.js
└── unit
├── .eslintrc
├── index.js
├── karma.conf.js
└── specs
├── components
├── App.spec.js
├── Counter.spec.js
├── Hello.spec.js
└── RouterChild.spec.js
└── store
├── createState.spec.js
├── mutations.spec.js
└── persistedState.spec.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions"]
7 | }
8 | }]
9 | ],
10 | "plugins": ["transform-runtime", "transform-object-rest-spread", "syntax-dynamic-import"],
11 | "env": {
12 | "test": {
13 | "presets": ["env"],
14 | "plugins": ["istanbul"]
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/
2 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // http://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parser: 'babel-eslint',
6 | parserOptions: {
7 | sourceType: 'module'
8 | },
9 | env: {
10 | browser: true,
11 | },
12 | extends: 'airbnb-base',
13 | // required to lint *.vue files
14 | plugins: [
15 | 'html'
16 | ],
17 | // check if imports actually resolve
18 | settings: {
19 | 'import/resolver': {
20 | webpack: {
21 | config: 'build/webpack.base.conf.js'
22 | }
23 | }
24 | },
25 | // add your custom rules here
26 | rules: {
27 | // don't require .vue extension when importing
28 | 'import/extensions': ['error', 'always', {
29 | 'js': 'never',
30 | 'vue': 'never'
31 | }],
32 | // allow optionalDependencies
33 | 'import/no-extraneous-dependencies': ['error', {
34 | 'optionalDependencies': ['test/unit/index.js']
35 | }],
36 | // allow debugger during development
37 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
38 | 'max-len': ['error', 120],
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set the default behavior, in case people don't have core.autocrlf set.
2 | * text=auto
3 |
4 | # Force LF on javascript, vue, and json files.
5 | *.js text eol=lf
6 | *.vue text eol=lf
7 | *.json text eol=lf
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | test/unit/coverage
8 | test/e2e/reports
9 | selenium-debug.log
10 |
11 | # Editor directories and files
12 | .idea
13 | .vscode
14 | *.suo
15 | *.ntvs*
16 | *.njsproj
17 | *.sln
18 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/carbon
2 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | // to edit target browsers: use "browserslist" field in package.json
6 | "autoprefixer": {}
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 | sudo: false
3 | addons:
4 | chrome: stable
5 | language: node_js
6 | node_js:
7 | - "6"
8 | - "8"
9 | - "10"
10 | script: "npm run unit"
11 | after_script: "npm install codecov.io && cat ./test/unit/coverage/lcov.info | codecov"
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [![Dependency Status][deps-image]][deps-url]
2 | [![Development Dependency Status][deps-dev-image]][deps-dev-url]
3 | [![Build Status][ci-image]][ci-url]
4 | [![Code Coverage status][codecov-image]][codecov-url]
5 |
6 | # vuejs-starterkit
7 |
8 | An opionated Vue.js PWA starter-kit project integrating `vue-router`, `vuex`, `vue-loader` and `webpack3` for non-trivial projects.
9 |
10 | ## Build Setup
11 |
12 | ``` bash
13 | # install dependencies
14 | npm install
15 |
16 | # serve with hot reload at localhost:8080
17 | npm run dev
18 |
19 | # build for production with minification
20 | npm run build
21 |
22 | # build for production and view the bundle analyzer report
23 | npm run build --report
24 |
25 | # run unit tests
26 | npm run unit
27 |
28 | # run e2e tests
29 | npm run e2e
30 |
31 | # run all tests
32 | npm test
33 | ```
34 |
35 | You can provide custom port-numbers via the environment variable `PORT`:
36 |
37 | ```sh
38 | # run dev-server on port 1337
39 | PORT=1337 node run dev
40 |
41 | # run e2e tests on port 8888
42 | PORT=8888 node run e2e
43 | ```
44 |
45 | 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).
46 |
47 | ## Demo
48 |
49 | Go to the [vuejs-starterkit-demo](https://macedigital.github.io/vuejs-starterkit/) demo page.
50 |
51 | ## Roadmap
52 |
53 | - [x] Fix aliases path-resolution issues with webpack.
54 | - [x] Fix issues with [vue-loader](https://vue-loader.vuejs.org/) webpack resolvers.
55 | - [ ] Add useful example components from [vue guide](https://vuejs.org/v2/guide/).
56 | - [x] Add more sophisticated [vue-router](https://router.vuejs.org/) examples.
57 | - [x] Add [vuex](https://vuex.vuejs.org/) integration for state-management.
58 | - [ ] Add first-class [Typescript support](https://www.typescriptlang.org/).
59 | - [ ] Revisit unit- and e2e-testing strategy, plenty of frameworks and styles to choose from.
60 |
61 | [deps-image]:https://img.shields.io/david/macedigital/vuejs-starterkit.svg?style=flat
62 | [deps-url]:https://david-dm.org/macedigital/vuejs-starterkit
63 | [deps-dev-image]:https://img.shields.io/david/dev/macedigital/vuejs-starterkit.svg?style=flat
64 | [deps-dev-url]:https://david-dm.org/macedigital/vuejs-starterkit?type=dev
65 | [ci-image]: https://img.shields.io/travis/macedigital/vuejs-starterkit.svg?style=flat
66 | [ci-url]: https://travis-ci.org/macedigital/vuejs-starterkit
67 | [codecov-image]:https://img.shields.io/codecov/c/github/macedigital/vuejs-starterkit.svg?style=flat
68 | [codecov-url]:https://codecov.io/github/macedigital/vuejs-starterkit
69 |
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')();
2 |
3 | process.env.NODE_ENV = 'production';
4 |
5 | const ora = require('ora');
6 | const rm = require('rimraf');
7 | const path = require('path');
8 | const chalk = require('chalk');
9 | const webpack = require('webpack');
10 | const config = require('../config');
11 | const webpackConfig = require('./webpack.prod.conf');
12 |
13 | const spinner = ora('building for production...');
14 | spinner.start();
15 |
16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), (err) => {
17 | if (err) throw err;
18 | webpack(webpackConfig, (error, stats) => {
19 | spinner.stop();
20 | if (error) throw error;
21 | process.stdout.write(`${stats.toString({
22 | colors: true,
23 | modules: false,
24 | children: false,
25 | chunks: false,
26 | chunkModules: false,
27 | })}\n\n`);
28 | // eslint-disable-next-line no-console
29 | console.log(chalk.cyan(' Build complete.\n'));
30 | // eslint-disable-next-line no-console
31 | console.log(chalk.yellow(`
32 | Tip: built files are meant to be served over an HTTP server.
33 | Opening index.html over file:// won't work.
34 | `));
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/build/check-versions.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 | const semver = require('semver');
3 | const packageConfig = require('../package.json');
4 | const shell = require('shelljs');
5 | const { execSync } = require('child_process');
6 |
7 | const exec = cmd => execSync(cmd).toString().trim();
8 |
9 | const versionRequirements = [
10 | {
11 | name: 'node',
12 | currentVersion: semver.clean(process.version),
13 | versionRequirement: packageConfig.engines.node,
14 | },
15 | ];
16 |
17 | if (shell.which('npm')) {
18 | versionRequirements.push({
19 | name: 'npm',
20 | currentVersion: exec('npm --version'),
21 | versionRequirement: packageConfig.engines.npm,
22 | });
23 | }
24 |
25 | module.exports = () => {
26 | const warnings = [];
27 | versionRequirements.forEach((mod) => {
28 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
29 | warnings.push(`${mod.name}: ${
30 | chalk.red(mod.currentVersion)} should be ${
31 | chalk.green(mod.versionRequirement)}`);
32 | }
33 | });
34 |
35 | if (warnings.length) {
36 | /* eslint-disable no-console */
37 | console.log(`\n${chalk.yellow('To use this template, you must update following to modules:')}\n`);
38 | warnings.forEach(warning => console.log(` ${warning}`));
39 | console.log();
40 | process.exit(1);
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/build/dev-client.js:
--------------------------------------------------------------------------------
1 | require('eventsource-polyfill');
2 | const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true');
3 |
4 | hotClient.subscribe((event) => {
5 | if (event.action === 'reload') {
6 | window.location.reload();
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/build/dev-server.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')();
2 |
3 | const config = require('../config');
4 |
5 | if (!process.env.NODE_ENV) {
6 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV);
7 | }
8 |
9 | const opn = require('opn');
10 | const path = require('path');
11 | const express = require('express');
12 | const webpack = require('webpack');
13 | const proxyMiddleware = require('http-proxy-middleware');
14 | const webpackConfig = process.env.NODE_ENV === 'testing'
15 | ? require('./webpack.prod.conf')
16 | : require('./webpack.dev.conf');
17 |
18 | // default port where dev server listens for incoming traffic
19 | const port = process.env.PORT || config.dev.port;
20 | // automatically open browser, if not set will be false
21 | const autoOpenBrowser = !!config.dev.autoOpenBrowser;
22 | // Define HTTP proxies to your custom API backend
23 | // https://github.com/chimurai/http-proxy-middleware
24 | const { proxyTable } = config.dev;
25 |
26 | const app = express();
27 | const compiler = webpack(webpackConfig);
28 |
29 | const devMiddleware = require('webpack-dev-middleware')(compiler, {
30 | publicPath: webpackConfig.output.publicPath,
31 | stats: 'errors-only',
32 | });
33 |
34 | const hotMiddleware = require('webpack-hot-middleware')(compiler, {
35 | log: false,
36 | });
37 | // force page reload when html-webpack-plugin template changes
38 | compiler.hooks.compilation.tap('html-webpack-plugin-after-emit', () => {
39 | hotMiddleware.publish({ action: 'reload' });
40 | });
41 |
42 | // proxy api requests
43 | Object.keys(proxyTable).forEach((context) => {
44 | let options = proxyTable[context];
45 | if (typeof options === 'string') {
46 | options = { target: options };
47 | }
48 | app.use(proxyMiddleware(options.filter || context, options));
49 | });
50 |
51 | // handle fallback for HTML5 history API
52 | app.use(require('connect-history-api-fallback')());
53 |
54 | // serve webpack bundle output
55 | app.use(devMiddleware);
56 |
57 | // enable hot-reload and state-preserving
58 | // compilation error display
59 | app.use(hotMiddleware);
60 |
61 | // serve pure static assets
62 | const staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory);
63 | app.use(staticPath, express.static('./static'));
64 |
65 | const uri = `http://localhost:${port}`;
66 |
67 | // eslint-disable-next-line no-underscore-dangle
68 | let _resolve;
69 | const readyPromise = new Promise((resolve) => {
70 | _resolve = resolve;
71 | });
72 |
73 | // eslint-disable-next-line no-console
74 | console.log('> Starting dev server...');
75 | devMiddleware.waitUntilValid(() => {
76 | // eslint-disable-next-line no-console
77 | console.log(`> Listening at ${uri}\n`);
78 | // when env is testing, don't need open it
79 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
80 | opn(uri);
81 | }
82 | _resolve();
83 | });
84 |
85 | const server = app.listen(port);
86 |
87 | module.exports = {
88 | ready: readyPromise,
89 | close: () => {
90 | server.close();
91 | },
92 | };
93 |
--------------------------------------------------------------------------------
/build/load-minified.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const UglifyJS = require('uglify-es');
3 |
4 | module.exports = (filePath) => {
5 | const code = fs.readFileSync(filePath, 'utf-8');
6 | const result = UglifyJS.minify(code);
7 | if (result.error) {
8 | return '';
9 | }
10 | return result.code;
11 | };
12 |
--------------------------------------------------------------------------------
/build/service-worker-dev.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | // This service worker file is effectively a 'no-op' that will reset any
4 | // previous service worker registered for the same host:port combination.
5 | // In the production build, this file is replaced with an actual service worker
6 | // file that will precache your site's local assets.
7 | // See https://github.com/facebookincubator/create-react-app/issues/2272#issuecomment-302832432
8 |
9 | self.addEventListener('install', () => self.skipWaiting());
10 |
11 | self.addEventListener('activate', () => {
12 | self.clients.matchAll({ type: 'window' }).then(windowClients => {
13 | for (let windowClient of windowClients) {
14 | // Force open pages to refresh, so that they have a chance to load the
15 | // fresh navigation response from the local dev server.
16 | windowClient.navigate(windowClient.url);
17 | }
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/build/service-worker-prod.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | (function() {
3 | 'use strict';
4 |
5 | // Check to make sure service workers are supported in the current browser,
6 | // and that the current page is accessed from a secure origin. Using a
7 | // service worker from an insecure origin will trigger JS console errors.
8 | const isLocalhost = Boolean(window.location.hostname === 'localhost' ||
9 | // [::1] is the IPv6 localhost address.
10 | window.location.hostname === '[::1]' ||
11 | // 127.0.0.1/8 is considered localhost for IPv4.
12 | window.location.hostname.match(
13 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
14 | )
15 | );
16 |
17 | window.addEventListener('load', function() {
18 | if ('serviceWorker' in navigator &&
19 | (window.location.protocol === 'https:' || isLocalhost)) {
20 | navigator.serviceWorker.register('service-worker.js')
21 | .then(function(registration) {
22 | // updatefound is fired if service-worker.js changes.
23 | registration.onupdatefound = function() {
24 | // updatefound is also fired the very first time the SW is installed,
25 | // and there's no need to prompt for a reload at that point.
26 | // So check here to see if the page is already controlled,
27 | // i.e. whether there's an existing service worker.
28 | if (navigator.serviceWorker.controller) {
29 | // The updatefound event implies that registration.installing is set
30 | const installingWorker = registration.installing;
31 |
32 | installingWorker.onstatechange = function() {
33 | switch (installingWorker.state) {
34 | case 'installed':
35 | // At this point, the old content will have been purged and the
36 | // fresh content will have been added to the cache.
37 | // It's the perfect time to display a "New content is
38 | // available; please refresh." message in the page's interface.
39 | break;
40 |
41 | case 'redundant':
42 | throw new Error('The installing ' +
43 | 'service worker became redundant.');
44 |
45 | default:
46 | // Ignore
47 | }
48 | };
49 | }
50 | };
51 | }).catch(function(e) {
52 | console.error('Error during service worker registration:', e);
53 | });
54 | }
55 | });
56 | })();
57 |
--------------------------------------------------------------------------------
/build/utils.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const config = require('../config');
3 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
4 |
5 | exports.assetsPath = (_path) => {
6 | const assetsSubDirectory = process.env.NODE_ENV === 'production'
7 | ? config.build.assetsSubDirectory
8 | : config.dev.assetsSubDirectory;
9 | return path.posix.join(assetsSubDirectory, _path);
10 | };
11 |
12 | exports.cssLoaders = (options) => {
13 | const opts = Object.assign({}, options);
14 |
15 | const cssLoader = {
16 | loader: 'css-loader',
17 | options: {
18 | minimize: process.env.NODE_ENV === 'production',
19 | sourceMap: opts.sourceMap,
20 | },
21 | };
22 |
23 | // generate loader string to be used with extract text plugin
24 | const generateLoaders = (loader, loaderOptions) => {
25 | const loaders = [cssLoader];
26 | if (loader) {
27 | loaders.push({
28 | loader: `${loader}-loader`,
29 | options: Object.assign({}, loaderOptions, {
30 | sourceMap: opts.sourceMap,
31 | }),
32 | });
33 | }
34 |
35 | // Extract CSS when that option is specified
36 | // (which is the case during production build)
37 | if (opts.extract) {
38 | return ExtractTextPlugin.extract({
39 | use: loaders,
40 | fallback: 'vue-style-loader',
41 | });
42 | }
43 | return ['vue-style-loader'].concat(loaders);
44 | };
45 |
46 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
47 | return {
48 | css: generateLoaders(),
49 | postcss: generateLoaders(),
50 | less: generateLoaders('less', { strictMath: true, strictUnits: true }),
51 | sass: generateLoaders('sass', { indentedSyntax: true }),
52 | scss: generateLoaders('sass'),
53 | stylus: generateLoaders('stylus'),
54 | styl: generateLoaders('stylus'),
55 | };
56 | };
57 |
58 | // Generate loaders for standalone style files (outside of .vue)
59 | exports.styleLoaders = (options) => {
60 | const output = [];
61 | const loaders = exports.cssLoaders(options);
62 | Object.keys(loaders).forEach((extension) => {
63 | const loader = loaders[extension];
64 | output.push({
65 | test: new RegExp(`\\.${extension}$`),
66 | use: loader,
67 | });
68 | });
69 |
70 | return output;
71 | };
72 |
73 | /**
74 | * Rewrite paths in `manifest.json` files
75 | * @link https://developer.mozilla.org/en-US/docs/Web/Manifest
76 | * @param {Buffer} content
77 | * @param {String} filepath
78 | * @returns {Buffer}
79 | */
80 | exports.updateManifest = (content, filepath) => {
81 | if (!filepath.match(/manifest\.json$/)) {
82 | return content;
83 | }
84 |
85 | const routePrefix = JSON.parse(config.build.env.ROUTER_PREFIX);
86 | const assetPrefix = config.build.assetsPublicPath;
87 | const addRoutePrefix = (route, prefix) => path.normalize(`${route.replace(/^\//, `${prefix}/`)}`);
88 | const iconPrefix = icon => addRoutePrefix(icon.src, assetPrefix);
89 | const json = JSON.parse(content);
90 |
91 | json.start_url = addRoutePrefix(json.start_url, routePrefix);
92 | json.scope = path.normalize(`${routePrefix}/`);
93 | json.icons = json.icons.map(icon => Object.assign(icon, { src: iconPrefix(icon) }));
94 |
95 | return Buffer.from(JSON.stringify(json), 'utf-8');
96 | };
97 |
--------------------------------------------------------------------------------
/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | const utils = require('./utils');
2 | const config = require('../config');
3 |
4 | const isProduction = process.env.NODE_ENV === 'production';
5 |
6 | module.exports = {
7 | loaders: utils.cssLoaders({
8 | sourceMap: isProduction
9 | ? config.build.productionSourceMap
10 | : config.dev.cssSourceMap,
11 | extract: isProduction,
12 | }),
13 | transformToRequire: {
14 | video: 'src',
15 | source: 'src',
16 | img: 'src',
17 | image: 'xlink:href',
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const friendlyFormatter = require('eslint-friendly-formatter');
3 | const { VueLoaderPlugin } = require('vue-loader');
4 |
5 | const utils = require('./utils');
6 | const config = require('../config');
7 | const vueLoaderConfig = require('./vue-loader.conf');
8 |
9 | const resolve = dir => path.join(__dirname, '..', dir);
10 |
11 | module.exports = {
12 | entry: {
13 | app: './src/main.js',
14 | },
15 | output: {
16 | path: config.build.assetsRoot,
17 | filename: '[name].js',
18 | publicPath: process.env.NODE_ENV === 'production'
19 | ? config.build.assetsPublicPath
20 | : config.dev.assetsPublicPath,
21 | },
22 | resolve: {
23 | extensions: ['.js', '.vue', '.json'],
24 | alias: {
25 | '@': resolve('src'),
26 | },
27 | },
28 | stats: 'minimal',
29 | plugins: [
30 | new VueLoaderPlugin(),
31 | ],
32 | module: {
33 | rules: [
34 | {
35 | test: /\.(js|vue)$/,
36 | loader: 'eslint-loader',
37 | enforce: 'pre',
38 | include: [resolve('src'), resolve('test')],
39 | options: {
40 | formatter: friendlyFormatter,
41 | },
42 | },
43 | {
44 | test: /\.vue$/,
45 | loader: 'vue-loader',
46 | options: vueLoaderConfig,
47 | },
48 | {
49 | test: /\.js$/,
50 | loader: 'babel-loader',
51 | include: [resolve('src'), resolve('test')],
52 | },
53 | {
54 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
55 | loader: 'url-loader',
56 | options: {
57 | limit: 4096,
58 | name: utils.assetsPath('img/[name].[hash:7].[ext]'),
59 | },
60 | },
61 | {
62 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
63 | loader: 'url-loader',
64 | options: {
65 | name: utils.assetsPath('media/[name].[hash:7].[ext]'),
66 | },
67 | },
68 | {
69 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
70 | loader: 'url-loader',
71 | options: {
72 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]'),
73 | },
74 | },
75 | ],
76 | },
77 | };
78 |
--------------------------------------------------------------------------------
/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const utils = require('./utils');
4 | const webpack = require('webpack');
5 | const config = require('../config');
6 | const merge = require('webpack-merge');
7 | const baseWebpackConfig = require('./webpack.base.conf');
8 | const HtmlWebpackPlugin = require('html-webpack-plugin');
9 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
10 |
11 | // add hot-reload related code to entry chunks
12 | Object.keys(baseWebpackConfig.entry).forEach((name) => {
13 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]);
14 | });
15 |
16 | module.exports = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }),
19 | },
20 | mode: 'development',
21 | optimization: {
22 | noEmitOnErrors: true,
23 | },
24 | // fast incremental rebuilds
25 | // @see https://webpack.js.org/configuration/devtool/ for a comparison
26 | devtool: '#cheap-module-eval-source-map',
27 | plugins: [
28 | new webpack.DefinePlugin({
29 | 'process.env': config.dev.env,
30 | }),
31 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
32 | new webpack.HotModuleReplacementPlugin(),
33 | // https://github.com/ampedandwired/html-webpack-plugin
34 | new HtmlWebpackPlugin({
35 | title: 'A Vuejs Starterkit',
36 | filename: 'index.html',
37 | template: 'index.html',
38 | inject: true,
39 | // eslint-disable-next-line max-len
40 | serviceWorkerLoader: ``,
41 | }),
42 | new FriendlyErrorsPlugin(),
43 | ],
44 | });
45 |
--------------------------------------------------------------------------------
/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const utils = require('./utils');
3 | const webpack = require('webpack');
4 | const config = require('../config');
5 | const merge = require('webpack-merge');
6 | const baseWebpackConfig = require('./webpack.base.conf');
7 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
8 | const CompressionWebpackPlugin = require('compression-webpack-plugin');
9 | const CopyWebpackPlugin = require('copy-webpack-plugin');
10 | const HtmlWebpackPlugin = require('html-webpack-plugin');
11 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
12 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin');
13 | const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
14 | const loadMinified = require('./load-minified');
15 |
16 | const env = process.env.NODE_ENV === 'testing'
17 | ? require('../config/test.env')
18 | : config.build.env;
19 |
20 | const webpackConfig = merge(baseWebpackConfig, {
21 | module: {
22 | rules: utils.styleLoaders({
23 | sourceMap: config.build.productionSourceMap,
24 | extract: true,
25 | }),
26 | },
27 | mode: 'production',
28 | devtool: config.build.productionSourceMap ? '#source-map' : false,
29 | output: {
30 | path: config.build.assetsRoot,
31 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
32 | publicPath: config.build.assetsPublicPath,
33 | crossOriginLoading: 'anonymous',
34 | },
35 | optimization: {
36 | removeAvailableModules: true,
37 | removeEmptyChunks: true,
38 | mergeDuplicateChunks: true,
39 | flagIncludedChunks: true,
40 | occurrenceOrder: true,
41 | sideEffects: true,
42 | providedExports: true,
43 | usedExports: true,
44 | concatenateModules: true,
45 | splitChunks: {
46 | cacheGroups: {
47 | vendors: {
48 | test: /[\\/]node_modules[\\/]/,
49 | name: 'vendors',
50 | priority: -20,
51 | chunks: 'all',
52 | },
53 | },
54 | },
55 | // chunk for the webpack runtime code and chunk manifest
56 | runtimeChunk: {
57 | name: 'manifest',
58 | },
59 | noEmitOnErrors: true,
60 | minimize: true,
61 | namedModules: true,
62 | namedChunks: true,
63 | },
64 | plugins: [
65 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
66 | new webpack.DefinePlugin({
67 | 'process.env': env,
68 | }),
69 | new ExtractTextPlugin({
70 | filename: utils.assetsPath('css/[name].[hash].css'),
71 | }),
72 | // Compress extracted CSS. We are using this plugin so that possible
73 | // duplicated CSS from different components can be deduped.
74 | new OptimizeCSSPlugin({
75 | cssProcessorOptions: {
76 | safe: true,
77 | },
78 | }),
79 | // generate dist index.html with correct asset hash for caching.
80 | // you can customize output by editing /index.html
81 | // see https://github.com/ampedandwired/html-webpack-plugin
82 | new HtmlWebpackPlugin({
83 | title: 'A Vuejs Starterkit',
84 | filename: process.env.NODE_ENV === 'testing'
85 | ? 'index.html'
86 | : config.build.index,
87 | template: 'index.html',
88 | inject: true,
89 | minify: {
90 | removeComments: true,
91 | collapseWhitespace: true,
92 | removeAttributeQuotes: true,
93 | // more options:
94 | // https://github.com/kangax/html-minifier#options-quick-reference
95 | },
96 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
97 | chunksSortMode: 'dependency',
98 | serviceWorkerLoader: ``,
99 | }),
100 | // copy custom static assets
101 | new CopyWebpackPlugin([
102 | {
103 | from: path.resolve(__dirname, '../static'),
104 | to: config.build.assetsSubDirectory,
105 | ignore: ['.*'],
106 | transform: (content, filepath) => utils.updateManifest(content, filepath),
107 | },
108 | ]),
109 | // service worker caching
110 | new SWPrecacheWebpackPlugin({
111 | cacheId: 'my-vue-app',
112 | filename: 'service-worker.js',
113 | staticFileGlobs: ['dist/**/*.{js,html,css}'],
114 | minify: true,
115 | stripPrefix: 'dist/',
116 | }),
117 | ],
118 | });
119 |
120 | if (config.build.productionGzip) {
121 | webpackConfig.plugins.push(new CompressionWebpackPlugin({
122 | asset: '[path].gz[query]',
123 | algorithm: 'gzip',
124 | test: new RegExp(`\\.(${config.build.productionGzipExtensions.join('|')})$`),
125 | threshold: 10240,
126 | minRatio: 0.8,
127 | }));
128 | }
129 |
130 | if (config.build.bundleAnalyzerReport) {
131 | webpackConfig.plugins.push(new BundleAnalyzerPlugin());
132 | }
133 |
134 | module.exports = webpackConfig;
135 |
--------------------------------------------------------------------------------
/build/webpack.test.conf.js:
--------------------------------------------------------------------------------
1 | // This is the webpack config used for unit tests.
2 |
3 | const utils = require('./utils');
4 | const webpack = require('webpack');
5 | const merge = require('webpack-merge');
6 | const baseConfig = require('./webpack.base.conf');
7 | const testEnv = require('../config/test.env');
8 |
9 | const webpackConfig = merge(baseConfig, {
10 | // use inline sourcemap for karma-sourcemap-loader
11 | module: {
12 | rules: utils.styleLoaders(),
13 | },
14 | mode: 'production',
15 | devtool: '#inline-source-map',
16 | resolveLoader: {
17 | alias: {
18 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option
19 | // see discussion at https://github.com/vuejs/vue-loader/issues/724
20 | 'scss-loader': 'sass-loader',
21 | },
22 | },
23 | plugins: [
24 | new webpack.DefinePlugin({
25 | 'process.env': testEnv,
26 | }),
27 | ],
28 | });
29 |
30 | // no need for app entry during tests
31 | delete webpackConfig.entry;
32 |
33 | module.exports = webpackConfig;
34 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge');
2 | const prodEnv = require('./prod.env');
3 |
4 | module.exports = merge(prodEnv, {
5 | NODE_ENV: '"development"',
6 | ROUTER_PREFIX: '"/"',
7 | });
8 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | // see http://vuejs-templates.github.io/webpack for documentation.
2 | const path = require('path');
3 |
4 | const prodEnv = require('./prod.env');
5 | const devEnv = require('./dev.env');
6 |
7 | module.exports = {
8 | build: {
9 | env: prodEnv,
10 | index: path.resolve(__dirname, '../dist/index.html'),
11 | assetsRoot: path.resolve(__dirname, '../dist'),
12 | assetsSubDirectory: 'static',
13 | assetsPublicPath: '/',
14 | productionSourceMap: true,
15 | // Gzip off by default as many popular static hosts such as
16 | // Surge or Netlify already gzip all static assets for you.
17 | // Before setting to `true`, make sure to:
18 | // npm install --save-dev compression-webpack-plugin
19 | productionGzip: false,
20 | productionGzipExtensions: ['js', 'css', 'html'],
21 | // Run the build command with an extra argument to
22 | // View the bundle analyzer report after build finishes:
23 | // `npm run build --report`
24 | // Set to `true` or `false` to always turn it on or off
25 | bundleAnalyzerReport: process.env.npm_config_report,
26 | },
27 | dev: {
28 | env: devEnv,
29 | port: process.env.PORT || 8080,
30 | autoOpenBrowser: true,
31 | assetsSubDirectory: 'static',
32 | assetsPublicPath: '/',
33 | proxyTable: {},
34 | // CSS Sourcemaps off by default because relative paths are "buggy"
35 | // with this option, according to the CSS-Loader README
36 | // (https://github.com/webpack/css-loader#sourcemaps)
37 | // In our experience, they generally work as expected,
38 | // just be aware of this issue when enabling this option.
39 | cssSourceMap: false,
40 | },
41 | };
42 |
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"',
3 | ROUTER_PREFIX: '"/vuejs-starterkit"',
4 | };
5 |
--------------------------------------------------------------------------------
/config/test.env.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge');
2 | const devEnv = require('./dev.env');
3 |
4 | module.exports = merge(devEnv, {
5 | NODE_ENV: '"testing"',
6 | });
7 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= htmlWebpackPlugin.options.title %>
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | <% for (var chunk of webpack.chunks) {
25 | for (var file of chunk.files) {
26 | if (file.match(/\.(js|css)$/)) { %>
27 | <% }}} %>
28 |
29 |
30 |
31 |
34 |
35 | <%= htmlWebpackPlugin.options.serviceWorkerLoader %>
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vuejs-starterkit",
3 | "version": "1.0.0",
4 | "description": "A Vue.js starter-kit project",
5 | "keywords": [
6 | "vue",
7 | "vuex",
8 | "vue-loader",
9 | "vue-router",
10 | "webpack"
11 | ],
12 | "author": "Matthias Adler",
13 | "private": true,
14 | "license": "MIT",
15 | "scripts": {
16 | "prepush": "npm run eslint",
17 | "dev": "node build/dev-server.js",
18 | "start": "npm run dev",
19 | "build": "node build/build.js",
20 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
21 | "e2e": "cross-env PORT=8081 node test/e2e/runner.js",
22 | "test": "npm run unit && npm run e2e",
23 | "eslint": "eslint --ext .js,.vue build config src test/unit/specs test/e2e/specs"
24 | },
25 | "dependencies": {
26 | "js-cookie": "^2.2.0",
27 | "vue": "^2.5.16",
28 | "vue-router": "^3.0.1",
29 | "vuex": "^3.0.0",
30 | "vuex-persistedstate": "^2.5.3",
31 | "vuex-router-sync": "^5.0.0"
32 | },
33 | "devDependencies": {
34 | "autoprefixer": "^8.5.0",
35 | "avoriaz": "^6.3.0",
36 | "babel-core": "^6.26.3",
37 | "babel-eslint": "^8.2.3",
38 | "babel-loader": "^7.1.4",
39 | "babel-plugin-istanbul": "^4.1.6",
40 | "babel-plugin-syntax-dynamic-import": "^6.18.0",
41 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
42 | "babel-plugin-transform-runtime": "^6.22.0",
43 | "babel-preset-env": "^1.7.0",
44 | "babel-register": "^6.26.0",
45 | "babel-runtime": "^6.26.0",
46 | "chai": "^4.1.2",
47 | "chalk": "^2.4.1",
48 | "chromedriver": "^2.38.3",
49 | "compression-webpack-plugin": "^1.1.11",
50 | "connect-history-api-fallback": "^1.5.0",
51 | "copy-webpack-plugin": "^4.5.1",
52 | "cross-env": "^5.1.5",
53 | "cross-spawn": "^6.0.5",
54 | "css-loader": "^0.28.11",
55 | "cssnano": "^3.10.0",
56 | "eslint": "^4.19.1",
57 | "eslint-config-airbnb-base": "^12.1.0",
58 | "eslint-friendly-formatter": "^4.0.1",
59 | "eslint-import-resolver-webpack": "^0.9.0",
60 | "eslint-loader": "^2.0.0",
61 | "eslint-plugin-html": "^4.0.3",
62 | "eslint-plugin-import": "^2.11.0",
63 | "eventsource-polyfill": "^0.9.6",
64 | "express": "^4.16.3",
65 | "extract-text-webpack-plugin": "^4.0.0-beta.0",
66 | "file-loader": "^1.1.11",
67 | "friendly-errors-webpack-plugin": "^1.7.0",
68 | "html-webpack-plugin": "^3.2.0",
69 | "http-proxy-middleware": "^0.18.0",
70 | "husky": "^0.14.3",
71 | "inject-loader": "^4.0.1",
72 | "karma": "^2.0.2",
73 | "karma-chrome-launcher": "^2.2.0",
74 | "karma-coverage": "^1.1.2",
75 | "karma-mocha": "^1.3.0",
76 | "karma-sinon-chai": "^1.3.4",
77 | "karma-sourcemap-loader": "^0.3.7",
78 | "karma-spec-reporter": "0.0.32",
79 | "karma-webpack": "^3.0.0",
80 | "less": "^3.0.4",
81 | "less-loader": "^4.1.0",
82 | "mocha": "^5.1.1",
83 | "nightwatch": "^0.9.21",
84 | "opn": "^5.3.0",
85 | "optimize-css-assets-webpack-plugin": "^4.0.1",
86 | "ora": "^2.1.0",
87 | "rimraf": "^2.6.2",
88 | "selenium-server": "^3.12.0",
89 | "semver": "^5.5.0",
90 | "shelljs": "^0.8.2",
91 | "sinon": "^4.5.0",
92 | "sinon-chai": "^3.0.0",
93 | "sw-precache": "^5.2.1",
94 | "sw-precache-webpack-plugin": "0.11.5",
95 | "uglify-es": "^3.3.10",
96 | "url-loader": "^1.0.1",
97 | "vue-loader": "^15.0.11",
98 | "vue-style-loader": "^4.1.0",
99 | "vue-template-compiler": "^2.5.16",
100 | "webpack": "^4.8.3",
101 | "webpack-bundle-analyzer": "^2.12.0",
102 | "webpack-dev-middleware": "^3.1.3",
103 | "webpack-hot-middleware": "^2.22.2",
104 | "webpack-merge": "^4.1.2"
105 | },
106 | "engines": {
107 | "node": ">= 6.9.0",
108 | "npm": ">= 3.10.0"
109 | },
110 | "browserslist": [
111 | "> 1%",
112 | "last 2 versions"
113 | ],
114 | "optionalDependencies": {
115 | "karma-firefox-launcher": "^1.1.0"
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Vue.js PWA
5 | Router
6 | Store
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macedigital/vuejs-starterkit/681dd3c71c01623965d810c12f92350bc7dd7009/src/assets/images/logo.png
--------------------------------------------------------------------------------
/src/assets/styles/_animations.less:
--------------------------------------------------------------------------------
1 | // @link https://vuejs.org/v2/guide/transitions.html
2 | .fade-enter-active, .fade-leave-active {
3 | transition: opacity .5s;
4 | }
5 | .fade-enter, .fade-leave-to {
6 | opacity: 0;
7 | }
8 |
9 | // main-transition animation
10 | .slide-fade-enter-active {
11 | transition: all .1s ease-out;
12 | }
13 | .slide-fade-leave-active {
14 | transition: all .1s ease-out
15 | }
16 | .slide-fade-enter, .slide-fade-leave-to {
17 | transform: translateX(1rem);
18 | opacity: 0;
19 | }
20 |
--------------------------------------------------------------------------------
/src/assets/styles/_variables.less:
--------------------------------------------------------------------------------
1 |
2 | // TYPOGRAPHY
3 | @font-family-base: 'Avenir', Helvetica, Arial, sans-serif;
4 | @font-color-base: #2c3e50;
5 |
6 | @header-background-color: #35495E;
7 | @header-text-color: #fff;
8 |
--------------------------------------------------------------------------------
/src/assets/styles/app.less:
--------------------------------------------------------------------------------
1 | @import "_variables.less";
2 |
3 | body {
4 | margin: 0;
5 | }
6 |
7 | a {
8 | text-decoration: none;
9 | }
10 |
11 | #app {
12 | font-family: @font-family-base;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | color: @font-color-base;
16 | }
17 |
18 | main {
19 | text-align: center;
20 | margin-top: 40px;
21 | }
22 |
23 | header {
24 | margin: 0;
25 | height: 56px;
26 | padding: 0 16px 0 24px;
27 | background-color: @header-background-color;
28 | color: @header-text-color;
29 | }
30 |
31 | header a {
32 | color: @header-text-color;
33 | display: inline-block;
34 | position: relative;
35 | font-size: 20px;
36 | line-height: 1;
37 | letter-spacing: .02em;
38 | font-weight: 400;
39 | box-sizing: border-box;
40 | padding-top: 16px;
41 | }
42 |
43 | @import "_animations.less";
44 |
--------------------------------------------------------------------------------
/src/components/Counter.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Synchronous mutations example
4 |
Current count: {{ count }}
5 |
6 | You can add {{ remaining }} more items.
7 | Only {{ remaining }} items remain!
8 | No more items!
9 |
10 |
11 |
12 |
Click buttons to change counter value.
13 |
14 |
15 |
16 |
39 |
40 |
45 |
46 |
--------------------------------------------------------------------------------
/src/components/Hello.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
Essential Links
5 |
12 |
Ecosystem
13 |
19 |
20 |
21 |
22 |
32 |
33 |
34 |
54 |
--------------------------------------------------------------------------------
/src/components/Router.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | vue-router
4 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
38 |
39 |
--------------------------------------------------------------------------------
/src/components/RouterChild.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ title | capitalize }}
4 |
This is some helpful text message
5 |
6 |
7 |
8 |
25 |
--------------------------------------------------------------------------------
/src/components/Store.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
19 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import { sync } from 'vuex-router-sync';
3 | import App from './App';
4 | import router from './router';
5 | import store from './store';
6 |
7 | Vue.config.productionTip = false;
8 |
9 | sync(store, router);
10 |
11 | /* eslint-disable no-new */
12 | new Vue({
13 | el: '#app',
14 | functional: true,
15 | router,
16 | store,
17 | render(h) {
18 | return h(App);
19 | },
20 | });
21 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Router from 'vue-router';
3 | import HelloComponent from '@/components/Hello';
4 | import RouterComponent from '@/components/Router';
5 | import RouterChildComponent from '@/components/RouterChild';
6 | import StoreComponent from '@/components/Store';
7 |
8 | Vue.use(Router);
9 |
10 | const router = new Router({
11 | mode: 'history',
12 | base: process.env.ROUTER_PREFIX,
13 | routes: [
14 | {
15 | path: '/',
16 | name: 'home',
17 | component: HelloComponent,
18 | },
19 | {
20 | path: '/store',
21 | name: 'store',
22 | alias: '/vuex',
23 | component: StoreComponent,
24 | },
25 | {
26 | path: '/router',
27 | component: RouterComponent,
28 | children: [
29 | {
30 | path: '',
31 | name: 'router',
32 | component: RouterChildComponent,
33 | },
34 | {
35 | path: ':child',
36 | component: RouterChildComponent,
37 | },
38 | ],
39 | },
40 | {
41 | path: '*',
42 | component: {
43 | functional: true,
44 | render(h) {
45 | return h('h1', 'Page not found!');
46 | },
47 | },
48 | },
49 | ],
50 | });
51 |
52 | export default router;
53 |
--------------------------------------------------------------------------------
/src/store/createState.js:
--------------------------------------------------------------------------------
1 | import { MIN_COUNT } from '@/store/mutations';
2 |
3 | const defaultState = {
4 | count: MIN_COUNT,
5 | };
6 |
7 | const createState = customState => Object.assign({}, defaultState, customState);
8 |
9 | export default createState;
10 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 | import mutations from './mutations';
4 | import persistedState from './persistedState';
5 | import createState from './createState';
6 |
7 | Vue.use(Vuex);
8 |
9 | export const createStore = initialState => new Vuex.Store({
10 | state: createState(initialState),
11 | plugins: [persistedState],
12 | mutations: {
13 | ...mutations,
14 | },
15 | });
16 |
17 | export default createStore();
18 |
--------------------------------------------------------------------------------
/src/store/mutations.js:
--------------------------------------------------------------------------------
1 | export const MIN_COUNT = 0;
2 | export const MAX_COUNT = 10;
3 |
4 | export default {
5 | increment(state) {
6 | if (state.count < MAX_COUNT) {
7 | // eslint-disable-next-line no-plusplus,no-param-reassign
8 | state.count++;
9 | }
10 | },
11 | decrement(state) {
12 | if (state.count > MIN_COUNT) {
13 | // eslint-disable-next-line no-plusplus,no-param-reassign
14 | state.count--;
15 | }
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/src/store/persistedState.js:
--------------------------------------------------------------------------------
1 | import createPersistedState from 'vuex-persistedstate';
2 | import * as Cookies from 'js-cookie';
3 |
4 | export const cookieStorage = {
5 | getItem: key => Cookies.get(key),
6 | // Please see https://github.com/js-cookie/js-cookie#json, on how to handle JSON.
7 | setItem: (key, value) => Cookies.set(key, value, { expires: 30, secure: process.env.NODE_ENV === 'production' }),
8 | removeItem: key => Cookies.remove(key),
9 | };
10 |
11 | export const persistedStateConfig = {
12 | key: '_vuex',
13 | filter({ type }) {
14 | // Don't store route state in cookie
15 | return !type.startsWith('route/');
16 | },
17 | storage: cookieStorage,
18 | };
19 |
20 | export default createPersistedState(persistedStateConfig);
21 |
--------------------------------------------------------------------------------
/static/img/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macedigital/vuejs-starterkit/681dd3c71c01623965d810c12f92350bc7dd7009/static/img/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/static/img/icons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macedigital/vuejs-starterkit/681dd3c71c01623965d810c12f92350bc7dd7009/static/img/icons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/static/img/icons/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macedigital/vuejs-starterkit/681dd3c71c01623965d810c12f92350bc7dd7009/static/img/icons/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/static/img/icons/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macedigital/vuejs-starterkit/681dd3c71c01623965d810c12f92350bc7dd7009/static/img/icons/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/static/img/icons/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macedigital/vuejs-starterkit/681dd3c71c01623965d810c12f92350bc7dd7009/static/img/icons/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/static/img/icons/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macedigital/vuejs-starterkit/681dd3c71c01623965d810c12f92350bc7dd7009/static/img/icons/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/static/img/icons/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macedigital/vuejs-starterkit/681dd3c71c01623965d810c12f92350bc7dd7009/static/img/icons/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/static/img/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macedigital/vuejs-starterkit/681dd3c71c01623965d810c12f92350bc7dd7009/static/img/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/static/img/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macedigital/vuejs-starterkit/681dd3c71c01623965d810c12f92350bc7dd7009/static/img/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/static/img/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macedigital/vuejs-starterkit/681dd3c71c01623965d810c12f92350bc7dd7009/static/img/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/static/img/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macedigital/vuejs-starterkit/681dd3c71c01623965d810c12f92350bc7dd7009/static/img/icons/favicon.ico
--------------------------------------------------------------------------------
/static/img/icons/msapplication-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macedigital/vuejs-starterkit/681dd3c71c01623965d810c12f92350bc7dd7009/static/img/icons/msapplication-icon-144x144.png
--------------------------------------------------------------------------------
/static/img/icons/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macedigital/vuejs-starterkit/681dd3c71c01623965d810c12f92350bc7dd7009/static/img/icons/mstile-150x150.png
--------------------------------------------------------------------------------
/static/img/icons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
150 |
--------------------------------------------------------------------------------
/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "VueJS Starterkit",
3 | "short_name": "Vue.js PWA",
4 | "description": "A webpack-based Vue.js starter-kit project for building PWAs.",
5 | "icons": [
6 | {
7 | "src": "/static/img/icons/android-chrome-192x192.png",
8 | "sizes": "192x192",
9 | "type": "image/png"
10 | },
11 | {
12 | "src": "/static/img/icons/android-chrome-512x512.png",
13 | "sizes": "512x512",
14 | "type": "image/png"
15 | }
16 | ],
17 | "start_url": "/?utm_source=web_app_manifest",
18 | "display": "standalone",
19 | "background_color": "#fff",
20 | "theme_color": "#4DBA87",
21 | "orientation": "portrait-primary",
22 | "prefer_related_applications": false
23 | }
24 |
--------------------------------------------------------------------------------
/test/e2e/custom-assertions/elementCount.js:
--------------------------------------------------------------------------------
1 | // A custom Nightwatch assertion.
2 | // the name of the method is the filename.
3 | // can be used in tests like this:
4 | //
5 | // browser.assert.elementCount(selector, count)
6 | //
7 | // for how to write custom assertions see
8 | // http://nightwatchjs.org/guide#writing-custom-assertions
9 | exports.assertion = function elementCount(selector, count) {
10 | this.message = `Testing if element <${selector}> has count: ${count}`;
11 | this.expected = count;
12 | this.pass = function pass(val) {
13 | return val === this.expected;
14 | };
15 | this.value = function value(res) {
16 | return res.value;
17 | };
18 | this.command = function command(cb) {
19 | const self = this;
20 | return this.api.execute(sel => document.querySelectorAll(sel).length, [selector], (res) => {
21 | cb.call(self, res);
22 | });
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/test/e2e/nightwatch.conf.js:
--------------------------------------------------------------------------------
1 | require('babel-register');
2 | const config = require('../../config');
3 | const chromedriver = require('chromedriver');
4 | const selenium = require('selenium-server');
5 |
6 | // http://nightwatchjs.org/gettingstarted#settings-file
7 | module.exports = {
8 | src_folders: ['test/e2e/specs'],
9 | output_folder: 'test/e2e/reports',
10 | custom_assertions_path: ['test/e2e/custom-assertions'],
11 |
12 | selenium: {
13 | start_process: true,
14 | server_path: selenium.path,
15 | host: '127.0.0.1',
16 | port: 4444,
17 | cli_args: {
18 | 'webdriver.chrome.driver': chromedriver.path,
19 | },
20 | },
21 |
22 | test_settings: {
23 | default: {
24 | selenium_port: 4444,
25 | selenium_host: '127.0.0.1',
26 | silent: true,
27 | globals: {
28 | devServerURL: `http://127.0.0.1:${process.env.PORT || config.dev.port}`,
29 | },
30 | },
31 |
32 | chrome: {
33 | desiredCapabilities: {
34 | browserName: 'chrome',
35 | javascriptEnabled: true,
36 | acceptSslCerts: true,
37 | },
38 | },
39 |
40 | firefox: {
41 | desiredCapabilities: {
42 | browserName: 'firefox',
43 | javascriptEnabled: true,
44 | acceptSslCerts: true,
45 | },
46 | },
47 | },
48 | };
49 |
--------------------------------------------------------------------------------
/test/e2e/runner.js:
--------------------------------------------------------------------------------
1 | // 1. start the dev server using production config
2 | process.env.NODE_ENV = 'testing';
3 | const server = require('../../build/dev-server.js');
4 | const spawn = require('cross-spawn');
5 |
6 | server.ready.then(() => {
7 | // 2. run the nightwatch test suite against it
8 | // to run in additional browsers:
9 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings"
10 | // 2. add it to the --env flag below
11 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
12 | // For more information on Nightwatch's config file, see
13 | // http://nightwatchjs.org/guide#settings-file
14 | let opts = process.argv.slice(2);
15 | if (opts.indexOf('--config') === -1) {
16 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']);
17 | }
18 | if (opts.indexOf('--env') === -1) {
19 | opts = opts.concat(['--env', 'chrome']);
20 | }
21 |
22 | const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' });
23 |
24 | runner.on('exit', (code) => {
25 | server.close();
26 | process.exit(code);
27 | });
28 |
29 | runner.on('error', (err) => {
30 | server.close();
31 | throw err;
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/test/e2e/specs/test.js:
--------------------------------------------------------------------------------
1 | // For authoring Nightwatch tests, see
2 | // http://nightwatchjs.org/guide#usage
3 |
4 | module.exports = {
5 | 'default e2e tests': function test(browser) {
6 | // automatically uses dev Server port from /config.index.js
7 | // default: http://localhost:8080
8 | // see nightwatch.conf.js
9 | const devServer = browser.globals.devServerURL;
10 |
11 | browser
12 | .url(devServer)
13 | .waitForElementVisible('#app', 5000)
14 | .assert.title('A Vuejs Starterkit')
15 | .assert.elementPresent('.hello')
16 | .assert.containsText('h1', 'Welcome to Your Vue.js PWA')
17 | .assert.elementCount('img', 1)
18 | .end();
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/test/unit/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "globals": {
6 | "expect": true,
7 | "sinon": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/unit/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 |
3 | Vue.config.productionTip = false;
4 | Vue.config.devtools = false;
5 |
6 | // require all test files (files that ends with .spec.js)
7 | const testsContext = require.context('./specs', true, /\.spec$/);
8 | testsContext.keys().forEach(testsContext);
9 |
10 | // require all src files except main.js for coverage.
11 | // you can also change this to match only the subset of files that
12 | // you want coverage for.
13 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/);
14 | srcContext.keys().forEach(srcContext);
15 |
--------------------------------------------------------------------------------
/test/unit/karma.conf.js:
--------------------------------------------------------------------------------
1 | // This is a karma config file. For more details see
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 | // we are also using it with karma-webpack
4 | // https://github.com/webpack/karma-webpack
5 |
6 | const webpackConfig = require('../../build/webpack.test.conf');
7 |
8 | module.exports = function karmaConfig(config) {
9 | config.set({
10 | frameworks: ['mocha', 'sinon-chai'],
11 | files: ['./index.js'],
12 | preprocessors: {
13 | './index.js': ['webpack', 'sourcemap'],
14 | },
15 | client: {
16 | chai: {
17 | includeStack: true,
18 | showDiff: true,
19 | },
20 | },
21 | browsers: ['ChromeHeadless'],
22 | port: 9876,
23 | colors: true,
24 | logLevel: config.LOG_INFO,
25 | autoWatch: false,
26 | singleRun: true,
27 | concurrency: Infinity,
28 | reporters: ['spec', 'coverage'],
29 | webpack: webpackConfig,
30 | webpackMiddleware: {
31 | noInfo: true,
32 | },
33 | coverageReporter: {
34 | dir: './coverage',
35 | reporters: [
36 | { type: 'lcov', subdir: '.' },
37 | { type: 'text-summary' },
38 | ],
39 | },
40 | customLaunchers: {
41 | ChromeHeadless: {
42 | base: 'Chrome',
43 | flags: [
44 | '--no-sandbox',
45 | '--headless',
46 | '--disable-gpu',
47 | '--disable-translate',
48 | '--disable-extensions',
49 | '--remote-debugging-port=9222', // Without a remote debugging port, Google Chrome exits immediately.
50 | ],
51 | },
52 | FirefoxHeadless: {
53 | base: 'Firefox',
54 | flags: [
55 | '--safe-mode',
56 | '--headless',
57 | '--marionette',
58 | ],
59 | },
60 | },
61 | });
62 | };
63 |
--------------------------------------------------------------------------------
/test/unit/specs/components/App.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 | import App from '@/App';
4 | import router from '@/router';
5 | import { shallow } from 'avoriaz';
6 |
7 | Vue.use(Vuex);
8 |
9 | describe('App.vue', () => {
10 | let wrapper;
11 | let path;
12 |
13 | beforeEach(() => {
14 | wrapper = shallow(App, {
15 | router,
16 | store: new Vuex.Store({}),
17 | });
18 | });
19 |
20 | [
21 | {
22 | routePath: '/',
23 | routeName: 'home',
24 | componentClass: '.hello',
25 | },
26 | {
27 | routePath: '/router',
28 | routeName: 'router',
29 | componentClass: '.router',
30 | },
31 | {
32 | routePath: '/store',
33 | routeName: 'store',
34 | componentClass: '.store',
35 | },
36 | ].forEach(({ routePath, routeName, componentClass }) => {
37 | describe(`when visiting path ${routePath}`, () => {
38 | beforeEach(() => {
39 | router.push({
40 | path: routePath,
41 | });
42 | });
43 |
44 | it(`should navigate to "${routeName}" route`, () => {
45 | expect(wrapper.vm.$route.name).to.equal(routeName);
46 | });
47 |
48 | it(`should render "${componentClass}" component`, () => {
49 | expect(wrapper.find(`main > ${componentClass}`).length).to.equal(1);
50 | });
51 | });
52 | });
53 |
54 | describe('When visiting an unregistered route', () => {
55 | beforeEach(() => {
56 | path = '/some/route/going/nowhere/';
57 | router.push({
58 | path,
59 | });
60 | });
61 |
62 | it('should be on visited path', () => {
63 | expect(wrapper.vm.$route.path).to.equal(path);
64 | });
65 |
66 | it('should render "Not found"', () => {
67 | expect(wrapper.find('main > h1')[0].text()).to.equal('Page not found!');
68 | });
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/test/unit/specs/components/Counter.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 | import { shallow } from 'avoriaz';
4 | import Counter from '@/components/Counter';
5 | import createState from '@/store/createState';
6 | import mutations, { MIN_COUNT, MAX_COUNT } from '@/store/mutations';
7 |
8 | Vue.use(Vuex);
9 |
10 | describe('Counter.vue', () => {
11 | let wrapper;
12 | let store;
13 | let initialCount;
14 |
15 | beforeEach(() => {
16 | initialCount = 3;
17 |
18 | store = new Vuex.Store({
19 | state: createState({
20 | count: initialCount,
21 | }),
22 | mutations: {
23 | ...mutations,
24 | },
25 | });
26 |
27 | wrapper = shallow(Counter, { store });
28 | });
29 |
30 | it('should show computed count value', () => {
31 | const preTag = wrapper.find('pre')[0].text();
32 | expect(preTag).to.equal(`Current count: ${initialCount}`);
33 | });
34 |
35 | it('should show remaing items value', () => {
36 | const remainingTag = wrapper.find('p')[0];
37 | const remainingItems = MAX_COUNT - initialCount;
38 | expect(remainingTag.text()).to.equal(`You can add ${remainingItems} more items.`);
39 | expect(remainingTag.hasClass('alert')).to.equal(false);
40 | });
41 |
42 | it('should decrement count when - button is clicked', () => {
43 | const expectedCount = initialCount - 1;
44 | wrapper.find('button')[0].trigger('click');
45 | expect(store.state.count).to.equal(expectedCount);
46 | });
47 |
48 | it('should increment count when + button is clicked', () => {
49 | const expectedCount = initialCount + 1;
50 | wrapper.find('button')[1].trigger('click');
51 | expect(store.state.count).to.equal(expectedCount);
52 | });
53 |
54 | describe('When counter is at minimum', () => {
55 | beforeEach(() => {
56 | store.state.count = MIN_COUNT;
57 | wrapper.update();
58 | });
59 |
60 | it('Should disable decrement button', () => {
61 | const btn = wrapper.find('button')[0];
62 | expect(btn.hasAttribute('disabled')).to.equal(true);
63 | });
64 | });
65 |
66 | describe('When count value is one less below limit', () => {
67 | beforeEach(() => {
68 | store.state.count = MAX_COUNT - 1;
69 | wrapper.update();
70 | });
71 |
72 | it('Should print alert message to user', () => {
73 | const remainingTag = wrapper.find('p')[0];
74 | expect(remainingTag.text()).to.equal('Only 1 items remain!');
75 | expect(remainingTag.hasClass('alert')).to.equal(true);
76 | });
77 | });
78 |
79 | describe('When counter is at maximum', () => {
80 | beforeEach(() => {
81 | store.state.count = MAX_COUNT;
82 | wrapper.update();
83 | });
84 |
85 | it('Should print alert message to user', () => {
86 | const remainingTag = wrapper.find('p')[0];
87 | expect(remainingTag.text()).to.equal('No more items!');
88 | expect(remainingTag.hasClass('alert')).to.equal(true);
89 | });
90 |
91 | it('Should disable increment button', () => {
92 | const btn = wrapper.find('button')[1];
93 | expect(btn.hasAttribute('disabled')).to.equal(true);
94 | });
95 | });
96 | });
97 |
--------------------------------------------------------------------------------
/test/unit/specs/components/Hello.spec.js:
--------------------------------------------------------------------------------
1 | import { shallow } from 'avoriaz';
2 | import Hello from '@/components/Hello';
3 |
4 | describe('Hello.vue', () => {
5 | let wrapper;
6 |
7 | beforeEach(() => {
8 | wrapper = shallow(Hello);
9 | });
10 |
11 | it('should render headline based on `msg` data-property', () => {
12 | const { msg } = wrapper.data();
13 | expect(wrapper.find('.hello h1')[0].text()).to.equal(msg);
14 | });
15 |
16 | it('should have reactive `msg` data-property', () => {
17 | const msg = 'Something else coming in';
18 | wrapper.setData({
19 | msg,
20 | });
21 | expect(wrapper.find('.hello h1')[0].text()).to.equal(msg);
22 | });
23 |
24 | it('should render 9 links', () => {
25 | expect(wrapper.find('.hello a').length).to.equal(9);
26 | });
27 |
28 | it('should render correct sub-headline with essential links', () => {
29 | expect(wrapper.find('.hello h2')[0].text()).to.equal('Essential Links');
30 | });
31 |
32 | it('should render correct sub-headline with ecosystem links', () => {
33 | expect(wrapper.find('.hello h2')[1].text()).to.equal('Ecosystem');
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/test/unit/specs/components/RouterChild.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Router from 'vue-router';
3 | import RouterChild from '@/components/RouterChild';
4 | import { shallow } from 'avoriaz';
5 |
6 | Vue.use(Router);
7 |
8 | const setupComponentWithRoute = ($route) => {
9 | const routes = [
10 | {
11 | path: '/:child',
12 | component: RouterChild,
13 | },
14 | ];
15 | const router = new Router({
16 | routes,
17 | });
18 |
19 | const wrapper = shallow(RouterChild, {
20 | router,
21 | });
22 |
23 | router.push($route);
24 | wrapper.update();
25 |
26 | return wrapper;
27 | };
28 |
29 | describe('RouterChild.vue', () => {
30 | it('should set Default title', () => {
31 | const $route = {
32 | path: '/',
33 | };
34 | const wrapper = setupComponentWithRoute($route);
35 | expect(wrapper.find('h1')[0].text()).to.equal('Default');
36 | });
37 |
38 | it('should set capitalized title passed from router parameter', () => {
39 | const $route = {
40 | path: '/something',
41 | };
42 | const wrapper = setupComponentWithRoute($route);
43 |
44 | expect(wrapper.vm.$route.params).to.deep.equal({
45 | child: 'something',
46 | });
47 |
48 | expect(wrapper.find('h1')[0].text()).to.equal('Something');
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/test/unit/specs/store/createState.spec.js:
--------------------------------------------------------------------------------
1 | import createState from '@/store/createState';
2 |
3 | describe('store:createState', () => {
4 | const defaultState = {
5 | count: 0,
6 | };
7 |
8 | it('should provide defaults', () => {
9 | expect(createState()).to.deep.equal(defaultState);
10 | });
11 |
12 | it('should merge custom state with default state', () => {
13 | const customState = {
14 | count: 5,
15 | other: 'thing',
16 | };
17 | expect(createState(customState)).to.deep.equal({
18 | count: 5,
19 | other: 'thing',
20 | });
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/test/unit/specs/store/mutations.spec.js:
--------------------------------------------------------------------------------
1 | import mutations, { MIN_COUNT, MAX_COUNT } from '@/store/mutations';
2 |
3 | describe('store:mutations', () => {
4 | describe('increment()', () => {
5 | it('should increment count by one', () => {
6 | const state = {
7 | count: MIN_COUNT,
8 | };
9 | mutations.increment(state);
10 | expect(state.count).to.equal(MIN_COUNT + 1);
11 | });
12 |
13 | it('should stop incrementing if maximum is reached', () => {
14 | const state = {
15 | count: MAX_COUNT,
16 | };
17 | mutations.increment(state);
18 | expect(state.count).to.equal(MAX_COUNT);
19 | });
20 | });
21 |
22 | describe('decrement()', () => {
23 | it('should decrement count by one', () => {
24 | const state = {
25 | count: MAX_COUNT,
26 | };
27 | mutations.decrement(state);
28 | expect(state.count).to.equal(MAX_COUNT - 1);
29 | });
30 |
31 | it('should stop decrementing if minimum is reached', () => {
32 | const state = {
33 | count: MIN_COUNT,
34 | };
35 | mutations.decrement(state);
36 | expect(state.count).to.equal(MIN_COUNT);
37 | });
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/test/unit/specs/store/persistedState.spec.js:
--------------------------------------------------------------------------------
1 | import { cookieStorage, persistedStateConfig } from '@/store/persistedState';
2 |
3 | describe('store:persistedState plugin', () => {
4 | describe('cookieStorage', () => {
5 | it('should set cookie values', () => {
6 | expect(cookieStorage.setItem('foo', 'bar')).to.match(/^foo=bar;/);
7 | });
8 |
9 | it('should read cookie values', () => {
10 | expect(cookieStorage.getItem('foo')).to.equal('bar');
11 | });
12 |
13 | it('should delete cookie values', () => {
14 | expect(cookieStorage.removeItem('foo')).to.equal(undefined);
15 | expect(cookieStorage.getItem('foo')).to.equal(undefined);
16 | });
17 | });
18 |
19 | describe('persistedStateConfig', () => {
20 | it('should have correct name', () => {
21 | expect(persistedStateConfig.key).to.equal('_vuex');
22 | });
23 |
24 | it('should filter out route changes', () => {
25 | expect(persistedStateConfig.filter({ type: 'route/' })).to.equal(false);
26 | });
27 |
28 | it('should permit non-route changes', () => {
29 | expect(persistedStateConfig.filter({ type: 'something/' })).to.equal(true);
30 | });
31 |
32 | it('should use cookieStorage', () => {
33 | expect(persistedStateConfig.storage).to.deep.equal(cookieStorage);
34 | });
35 | });
36 | });
37 |
--------------------------------------------------------------------------------