0)&&r.host.split("@"))&&(r.auth=R.shift(),r.host=r.hostname=R.shift());return r.search=t.search,r.query=t.query,o.isNull(r.pathname)&&o.isNull(r.search)||(r.path=(r.pathname?r.pathname:"")+(r.search?r.search:"")),r.href=r.format(),r}if(!j.length)return r.pathname=null,r.search?r.path="/"+r.search:r.path=null,r.href=r.format(),r;for(var O=j.slice(-1)[0],C=(r.host||t.host||j.length>1)&&("."===O||".."===O)||""===O,I=0,U=j.length;U>=0;U--)"."===(O=j[U])?j.splice(U,1):".."===O?(j.splice(U,1),I++):I&&(j.splice(U,1),I--);if(!b&&!w)for(;I--;I)j.unshift("..");!b||""===j[0]||j[0]&&"/"===j[0].charAt(0)||j.unshift(""),C&&"/"!==j.join("/").substr(-1)&&j.push("");var R,z=""===j[0]||j[0]&&"/"===j[0].charAt(0);x&&(r.hostname=r.host=z?"":j.length?j.shift():"",(R=!!(r.host&&r.host.indexOf("@")>0)&&r.host.split("@"))&&(r.auth=R.shift(),r.host=r.hostname=R.shift()));return(b=b||r.host&&j.length)&&!z&&j.unshift(""),j.length?r.pathname=j.join("/"):(r.pathname=null,r.path=null),o.isNull(r.pathname)&&o.isNull(r.search)||(r.path=(r.pathname?r.pathname:"")+(r.search?r.search:"")),r.auth=t.auth||r.auth,r.slashes=r.slashes||t.slashes,r.href=r.format(),r},f.prototype.parseHost=function(){var t=this.host,e=d.exec(t);e&&(":"!==(e=e[0])&&(this.port=e.substr(1)),t=t.substr(0,t.length-e.length)),t&&(this.hostname=t)};var I={parse:a,resolve:u,resolveObject:c,format:l,Url:p};var U="https://github.com/raychenfj/v-wechat-auth",R=["appId","scope","authorize"],z={autoRedirect:!0,state:"",authorize:function(){console.error("should implement authorize method in options")}};function S(t){return t&&"function"==typeof t}var $=function(t){var e=this;this.options=Object.assign(z,t),R.forEach(function(t){e.options[t]||console.error("required property "+t+" is missing in options, please visit "+U+" for more info.")}),this.user=null};return $.prototype.authorize=function(t,e){var r=this,o=I.parse(window.location.href,!0);if(o.query&&!o.query.code)return delete o.query.state,delete o.search,this.redirect(I.format(o));var n=function(e){var o=r.onSuccess(e);return S(t)&&t(o),o},s=function(t){r.onFail(t),S(e)&&e(t)};try{var h=this.options.authorize(o.query.code,n,s);if(h&&h instanceof Promise)return h.then(n).catch(s)}catch(t){s(t)}},$.prototype.onSuccess=function(t){if(!t.openid&&this.options.autoRedirect){var e=I.parse(window.location.href,!0);return delete e.query.code,delete e.query.state,delete e.search,this.redirect(I.format(e))}return this.user=t,this.user},$.prototype.onFail=function(t){console.error("error occurs when authorize from back end"),console.error(t)},$.prototype.redirect=function(t){var e=this.options;window.location.href="https://open.weixin.qq.com/connect/oauth2/authorize?appid="+e.appId+"&redirect_uri="+encodeURIComponent(t)+"&response_type=code&scope="+e.scope+"&state="+e.state+"#wechat_redirect"},$.install=function t(e,r){t.installed||(t.installed=!0,Object.defineProperty(e.prototype,"$wechatAuth",{get:function(){return this.$root._wechatAuth}}),e.mixin({beforeCreate:function(){this.$options.wechatAuth&&(this._wechatAuth=this.$options.wechatAuth)}}))},$});
--------------------------------------------------------------------------------
/examples/config.example.js:
--------------------------------------------------------------------------------
1 | var config = {
2 | appId: '', // your wechat appid,
3 | scope: 'snsapi_userinfo', // snsapi_base or snsapi_userinfo
4 | /**
5 | *
6 | * @param {*} code
7 | * @param {*} success if you use callback, don't return anything in the function, and call success and pass response data to it
8 | * @param {*} fail
9 | */
10 | authorize (code, success, fail) {
11 | return axios.get('your backend api here', { params: { code: code } })
12 | .then(function (res) {
13 | var data = (res && res.data) || {} // response data should at least contain openid
14 | return data
15 | })
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | v-wechat-auth example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | your openid is: {{user.openid}}
15 |
16 |
17 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/images/openid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raychenfj/v-wechat-auth/42e6e1b6f5070b6adecbec7b59b4d22bc423a836/images/openid.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "v-wechat-auth",
3 | "version": "1.0.0",
4 | "description": "wechat auth plugin for vue 2.0",
5 | "author": "fengjun.chen ",
6 | "main": "dist/v-wechat-auth.common.js",
7 | "module": "dist/v-wechat-auth.esm.js",
8 | "browser": "dist/v-wechat-auth.js",
9 | "unpkg": "dist/v-wechat-auth.js",
10 | "style": "dist/v-wechat-auth.css",
11 | "files": [
12 | "dist",
13 | "src"
14 | ],
15 | "scripts": {
16 | "clean": "rimraf dist",
17 | "build": "node build/build.js",
18 | "build:dll": "webpack --progress --config build/webpack.config.dll.js",
19 | "lint": "yon run lint:js && yon run lint:css",
20 | "lint:js": "eslint --ext js --ext jsx --ext vue src test/**/*.spec.js test/*.js build",
21 | "lint:js:fix": "yon run lint:js -- --fix",
22 | "lint:css": "stylelint src/**/*.{vue,css}",
23 | "lint:staged": "lint-staged",
24 | "pretest": "yon run lint",
25 | "test": "cross-env BABEL_ENV=test karma start test/karma.conf.js --single-run",
26 | "dev": "webpack-dashboard -- webpack-dev-server --config build/webpack.config.dev.js --open",
27 | "dev:coverage": "cross-env BABEL_ENV=test karma start test/karma.conf.js",
28 | "prepublish": "yon run build",
29 | "example": "sudo lite-server -c ./bs-config.json"
30 | },
31 | "lint-staged": {
32 | "*.{vue,jsx,js}": [
33 | "eslint --fix"
34 | ],
35 | "*.{vue,css}": [
36 | "stylefmt",
37 | "stylelint"
38 | ]
39 | },
40 | "pre-commit": "lint:staged",
41 | "devDependencies": {
42 | "add-asset-html-webpack-plugin": "^2.0.0",
43 | "babel-core": "^6.24.0",
44 | "babel-eslint": "^7.2.0",
45 | "babel-helper-vue-jsx-merge-props": "^2.0.0",
46 | "babel-loader": "^7.0.0",
47 | "babel-plugin-external-helpers": "^6.22.0",
48 | "babel-plugin-istanbul": "^4.1.0",
49 | "babel-plugin-syntax-jsx": "^6.18.0",
50 | "babel-plugin-transform-object-rest-spread": "^6.23.0",
51 | "babel-plugin-transform-runtime": "^6.23.0",
52 | "babel-plugin-transform-vue-jsx": "^3.4.0",
53 | "babel-preset-env": "^1.4.0",
54 | "buble": "^0.15.2",
55 | "chai": "^3.5.0",
56 | "chai-dom": "^1.4.0",
57 | "clean-css": "^4.0.0",
58 | "cross-env": "^4.0.0",
59 | "css-loader": "^0.28.0",
60 | "eslint": "^3.19.0",
61 | "eslint-config-vue": "^2.0.0",
62 | "eslint-plugin-vue": "^2.0.0",
63 | "extract-text-webpack-plugin": "^2.1.0",
64 | "html-webpack-plugin": "^2.28.0",
65 | "karma": "^1.7.0",
66 | "karma-chai-dom": "^1.1.0",
67 | "karma-chrome-launcher": "^2.1.0",
68 | "karma-coverage": "^1.1.0",
69 | "karma-mocha": "^1.3.0",
70 | "karma-sinon-chai": "^1.3.0",
71 | "karma-sourcemap-loader": "^0.3.7",
72 | "karma-spec-reporter": "^0.0.31",
73 | "karma-webpack": "^2.0.0",
74 | "lint-staged": "^3.4.0",
75 | "lite-server": "^2.3.0",
76 | "mkdirp": "^0.5.1",
77 | "mocha": "^3.3.0",
78 | "mocha-css": "^1.0.1",
79 | "postcss": "^6.0.0",
80 | "postcss-cssnext": "^2.10.0",
81 | "pre-commit": "^1.2.0",
82 | "rimraf": "^2.6.0",
83 | "rollup": "^0.41.6",
84 | "rollup-plugin-buble": "^0.15.0",
85 | "rollup-plugin-commonjs": "^8.0.0",
86 | "rollup-plugin-jsx": "^1.0.0",
87 | "rollup-plugin-node-resolve": "^3.0.0",
88 | "rollup-plugin-postcss": "^0.4.1",
89 | "rollup-plugin-replace": "^1.1.0",
90 | "rollup-plugin-vue": "^2.3.0",
91 | "sinon": "2.2.0",
92 | "sinon-chai": "^2.10.0",
93 | "style-loader": "^0.17.0",
94 | "stylefmt": "^5.3.0",
95 | "stylelint": "^7.10.0",
96 | "stylelint-config-standard": "^16.0.0",
97 | "stylelint-processor-html": "^1.0.0",
98 | "uglify-js": "^3.0.0",
99 | "uppercamelcase": "^3.0.0",
100 | "vue": "^2.3.0",
101 | "vue-loader": "^12.0.0",
102 | "vue-template-compiler": "^2.3.0",
103 | "webpack": "^2.5.0",
104 | "webpack-bundle-analyzer": "^2.4.0",
105 | "webpack-dashboard": "^0.4.0",
106 | "webpack-dev-server": "^2.4.0",
107 | "webpack-merge": "^4.0.0",
108 | "yarn-or-npm": "^2.0.0"
109 | },
110 | "peerDependencies": {
111 | "vue": "^2.3.0"
112 | },
113 | "dllPlugin": {
114 | "name": "vuePluginTemplateDeps",
115 | "include": [
116 | "mocha/mocha.js",
117 | "style-loader!css-loader!mocha-css",
118 | "html-entities",
119 | "vue/dist/vue.js",
120 | "chai",
121 | "core-js/library",
122 | "url",
123 | "sockjs-client",
124 | "vue-style-loader/lib/addStylesClient.js",
125 | "events",
126 | "ansi-html",
127 | "style-loader/addStyles.js"
128 | ]
129 | },
130 | "repository": {
131 | "type": "git",
132 | "url": "git+https://github.com/raychenfj/v-wechat-auth.git"
133 | },
134 | "bugs": {
135 | "url": "https://github.com/raychenfj/v-wechat-auth/issues"
136 | },
137 | "homepage": "https://github.com/raychenfj/v-wechat-auth#readme",
138 | "license": {
139 | "type": "MIT",
140 | "url": "http://www.opensource.org/licenses/mit-license.php"
141 | },
142 | "dependencies": {
143 | "url": "^0.11.0"
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | git: 'https://github.com/raychenfj/v-wechat-auth'
3 | }
4 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import url from 'url'
2 |
3 | import install from './install'
4 | import config from './config'
5 |
6 | const requiredProps = ['appId', 'scope', 'authorize']
7 | const defaultOptions = {
8 | autoRedirect: true,
9 | state: '',
10 | authorize () {
11 | console.error('should implement authorize method in options')
12 | }
13 | }
14 |
15 | function isFunction (fn) {
16 | return fn && typeof fn === 'function'
17 | }
18 |
19 | export default class WechatAuth {
20 | /**
21 | * @typedef {Object} Options wechat auth options
22 | * @property {boolean} autoRedirect optional, auto redirect to wechat oauth url when there is no code in query or no openid in ajax response
23 | * @property {string} appId required, wechat appId
24 | * @property {string} scope required, wechat auth scope, snsapi_base or snsapi_userinfo
25 | * @property {string} state optional, wechat state
26 | * @property {function} authorize required, an ajax call to back end and should return an object contains openid at least, support promise or callback
27 | * @property {boolean} ssr optional, used in server side render, feature not implement yet
28 | */
29 |
30 | /**
31 | * Create a wechat auth object
32 | * @constructor
33 | * @param {Options} options
34 | */
35 | constructor (options) {
36 | this.options = Object.assign(defaultOptions, options)
37 |
38 | requiredProps.forEach(prop => {
39 | if (!this.options[prop]) {
40 | console.error(`required property ${prop} is missing in options, please visit ${config.git} for more info.`)
41 | }
42 | })
43 |
44 | this.user = null
45 | }
46 |
47 | /**
48 | * authorize
49 | * @param {*} onSuccess
50 | * @param {*} onFail
51 | * @returns {Promise}
52 | */
53 | authorize (onSuccess, onFail) {
54 | const urlObj = url.parse(window.location.href, true)
55 |
56 | if (urlObj.query && !urlObj.query.code) {
57 | // delete state in query in url
58 | delete urlObj.query.state
59 | delete urlObj.search
60 | return this.redirect(url.format(urlObj))
61 | }
62 |
63 | // decorated success
64 | const success = (data) => {
65 | const user = this.onSuccess(data)
66 | if (isFunction(onSuccess)) {
67 | onSuccess(user)
68 | }
69 | return user
70 | }
71 |
72 | // decorated fail
73 | const fail = (e) => {
74 | this.onFail(e)
75 | if (isFunction(onFail)) {
76 | onFail(e)
77 | }
78 | }
79 |
80 | try {
81 | // if options.authorize use callback
82 | const promise = this.options.authorize(urlObj.query.code, success, fail)
83 |
84 | // if options.authorize return promise
85 | if (promise && promise instanceof Promise) {
86 | return promise.then(success).catch(fail)
87 | }
88 | } catch (e) {
89 | fail(e)
90 | }
91 | }
92 |
93 | /**
94 | * onSuccess
95 | * @private
96 | * @param {*} data
97 | */
98 | onSuccess (data) {
99 | if (!data.openid && this.options.autoRedirect) {
100 | const urlObj = url.parse(window.location.href, true)
101 | // delete code and state in query in url
102 | delete urlObj.query.code
103 | delete urlObj.query.state
104 | delete urlObj.search
105 | return this.redirect(url.format(urlObj))
106 | }
107 | this.user = data
108 | return this.user
109 | }
110 |
111 | /**
112 | * onFail
113 | * @private
114 | * @param {*} e
115 | */
116 | onFail (e) {
117 | console.error('error occurs when authorize from back end')
118 | console.error(e)
119 | }
120 |
121 | /**
122 | * redirect to wechat auth url
123 | * @param {*} url
124 | */
125 | redirect (url) {
126 | const options = this.options
127 | window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${options.appId}&redirect_uri=${encodeURIComponent(url)}&response_type=code&scope=${options.scope}&state=${options.state}#wechat_redirect`
128 | }
129 | }
130 |
131 | WechatAuth.install = install
132 |
--------------------------------------------------------------------------------
/src/install.js:
--------------------------------------------------------------------------------
1 | export default function install (Vue, options) {
2 | if (install.installed) return
3 | install.installed = true
4 |
5 | Object.defineProperty(Vue.prototype, '$wechatAuth', {
6 | get () { return this.$root._wechatAuth }
7 | })
8 |
9 | Vue.mixin({
10 | beforeCreate () {
11 | if (this.$options.wechatAuth) {
12 | this._wechatAuth = this.$options.wechatAuth
13 | }
14 | }
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "globals": {
6 | "expect": true,
7 | "sinon": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/helpers/Test.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
39 |
40 |
108 |
--------------------------------------------------------------------------------
/test/helpers/index.js:
--------------------------------------------------------------------------------
1 | import camelcase from 'camelcase'
2 | import { createVM, Vue } from './utils'
3 | import { nextTick } from './wait-for-update'
4 |
5 | export function dataPropagationTest (Component) {
6 | return function () {
7 | const spy = sinon.spy()
8 | const vm = createVM(this, function (h) {
9 | return (
10 | Hello
11 | )
12 | })
13 | spy.should.have.not.been.called
14 | vm.$('.custom').should.exist
15 | vm.$('.custom').click()
16 | spy.should.have.been.calledOnce
17 | }
18 | }
19 |
20 | export function attrTest (it, base, Component, attr) {
21 | const attrs = Array.isArray(attr) ? attr : [attr]
22 |
23 | attrs.forEach(attr => {
24 | it(attr, function (done) {
25 | const vm = createVM(this, function (h) {
26 | const opts = {
27 | props: {
28 | [camelcase(attr)]: this.active
29 | }
30 | }
31 | return (
32 | {attr}
33 | )
34 | }, {
35 | data: { active: true }
36 | })
37 | vm.$(`.${base}`).should.have.class(`${base}--${attr}`)
38 | vm.active = false
39 | nextTick().then(() => {
40 | vm.$(`.${base}`).should.not.have.class(`${base}--${attr}`)
41 | vm.active = true
42 | }).then(done)
43 | })
44 | })
45 | }
46 |
47 | export {
48 | createVM,
49 | Vue,
50 | nextTick
51 | }
52 |
--------------------------------------------------------------------------------
/test/helpers/utils.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue/dist/vue.js'
2 | import Test from './Test.vue'
3 |
4 | Vue.config.productionTip = false
5 | const isKarma = !!window.__karma__
6 |
7 | export function createVM (context, template, opts = {}) {
8 | return isKarma
9 | ? createKarmaTest(context, template, opts)
10 | : createVisualTest(context, template, opts)
11 | }
12 |
13 | const emptyNodes = document.querySelectorAll('nonexistant')
14 | Vue.prototype.$$ = function $$ (selector) {
15 | const els = document.querySelectorAll(selector)
16 | const vmEls = this.$el.querySelectorAll(selector)
17 | const fn = vmEls.length
18 | ? el => vmEls.find(el)
19 | : el => this.$el === el
20 | const found = Array.from(els).filter(fn)
21 | return found.length
22 | ? found
23 | : emptyNodes
24 | }
25 |
26 | Vue.prototype.$ = function $ (selector) {
27 | const els = document.querySelectorAll(selector)
28 | const vmEl = this.$el.querySelector(selector)
29 | const fn = vmEl
30 | ? el => el === vmEl
31 | : el => el === this.$el
32 | // Allow should chaining for tests
33 | return Array.from(els).find(fn) || emptyNodes
34 | }
35 |
36 | export function createKarmaTest (context, template, opts) {
37 | const el = document.createElement('div')
38 | document.getElementById('tests').appendChild(el)
39 | const render = typeof template === 'string'
40 | ? { template: `${template}
` }
41 | : { render: template }
42 | return new Vue({
43 | el,
44 | name: 'Test',
45 | ...render,
46 | ...opts
47 | })
48 | }
49 |
50 | export function createVisualTest (context, template, opts) {
51 | let vm
52 | if (typeof template === 'string') {
53 | opts.components = opts.components || {}
54 | // Let the user define a test component
55 | if (!opts.components.Test) {
56 | opts.components.Test = Test
57 | }
58 | vm = new Vue({
59 | name: 'TestContainer',
60 | el: context.DOMElement,
61 | template: `${template}`,
62 | ...opts
63 | })
64 | } else {
65 | // TODO allow redefinition of Test component
66 | vm = new Vue({
67 | name: 'TestContainer',
68 | el: context.DOMElement,
69 | render (h) {
70 | return h(Test, {
71 | attrs: {
72 | id: context.DOMElement.id
73 | }
74 | // render the passed component with this scope
75 | }, [template.call(this, h)])
76 | },
77 | ...opts
78 | })
79 | }
80 |
81 | context.DOMElement.vm = vm
82 | return vm
83 | }
84 |
85 | export function register (name, component) {
86 | Vue.component(name, component)
87 | }
88 |
89 | export { isKarma, Vue }
90 |
--------------------------------------------------------------------------------
/test/helpers/wait-for-update.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue/dist/vue.js'
2 |
3 | // Testing helper
4 | // nextTick().then(() => {
5 | //
6 | // Automatically waits for nextTick
7 | // }).then(() => {
8 | // return a promise or value to skip the wait
9 | // })
10 | function nextTick () {
11 | const jobs = []
12 | let done
13 |
14 | const chainer = {
15 | then (cb) {
16 | jobs.push(cb)
17 | return chainer
18 | }
19 | }
20 |
21 | function shift (...args) {
22 | const job = jobs.shift()
23 | let result
24 | try {
25 | result = job(...args)
26 | } catch (e) {
27 | jobs.length = 0
28 | done(e)
29 | }
30 |
31 | // wait for nextTick
32 | if (result !== undefined) {
33 | if (result.then) {
34 | result.then(shift)
35 | } else {
36 | shift(result)
37 | }
38 | } else if (jobs.length) {
39 | requestAnimationFrame(() => Vue.nextTick(shift))
40 | }
41 | }
42 |
43 | // First time
44 | Vue.nextTick(() => {
45 | done = jobs[jobs.length - 1]
46 | if (done.toString().slice(0, 14) !== 'function (err)') {
47 | throw new Error('waitForUpdate chain is missing .then(done)')
48 | }
49 | shift()
50 | })
51 |
52 | return chainer
53 | }
54 |
55 | exports.nextTick = nextTick
56 | exports.delay = time => new Promise(resolve => setTimeout(resolve, time))
57 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | // Polyfill fn.bind() for PhantomJS
2 | import bind from 'function-bind'
3 | /* eslint-disable no-extend-native */
4 | Function.prototype.bind = bind
5 |
6 | // Polyfill Object.assign for PhantomJS
7 | import objectAssign from 'object-assign'
8 | Object.assign = objectAssign
9 |
10 | // require all src files 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, /^\.\/(?!index(\.js)?$)/)
14 | srcContext.keys().forEach(srcContext)
15 |
16 | // Use a div to insert elements
17 | before(function () {
18 | const el = document.createElement('DIV')
19 | el.id = 'tests'
20 | document.body.appendChild(el)
21 | })
22 |
23 | // Remove every test html scenario
24 | afterEach(function () {
25 | const el = document.getElementById('tests')
26 | for (let i = 0; i < el.children.length; ++i) {
27 | el.removeChild(el.children[i])
28 | }
29 | })
30 |
31 | const specsContext = require.context('./specs', true)
32 | specsContext.keys().forEach(specsContext)
33 |
--------------------------------------------------------------------------------
/test/karma.conf.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge')
2 | const baseConfig = require('../build/webpack.config.dev.js')
3 |
4 | const webpackConfig = merge(baseConfig, {
5 | // use inline sourcemap for karma-sourcemap-loader
6 | devtool: '#inline-source-map'
7 | })
8 |
9 | webpackConfig.plugins = []
10 |
11 | const vueRule = webpackConfig.module.rules.find(rule => rule.loader === 'vue-loader')
12 | vueRule.options = vueRule.options || {}
13 | vueRule.options.loaders = vueRule.options.loaders || {}
14 | vueRule.options.loaders.js = 'babel-loader'
15 |
16 | // no need for app entry during tests
17 | delete webpackConfig.entry
18 |
19 | module.exports = function (config) {
20 | config.set({
21 | // to run in additional browsers:
22 | // 1. install corresponding karma launcher
23 | // http://karma-runner.github.io/0.13/config/browsers.html
24 | // 2. add it to the `browsers` array below.
25 | browsers: ['Chrome'],
26 | frameworks: ['mocha', 'chai-dom', 'sinon-chai'],
27 | reporters: ['spec', 'coverage'],
28 | files: ['./index.js'],
29 | preprocessors: {
30 | './index.js': ['webpack', 'sourcemap']
31 | },
32 | webpack: webpackConfig,
33 | webpackMiddleware: {
34 | noInfo: true
35 | },
36 | coverageReporter: {
37 | dir: './coverage',
38 | reporters: [
39 | { type: 'lcov', subdir: '.' },
40 | { type: 'text-summary' }
41 | ]
42 | }
43 | })
44 | }
45 |
--------------------------------------------------------------------------------
/test/visual.js:
--------------------------------------------------------------------------------
1 | import 'style-loader!css-loader!mocha-css'
2 |
3 | // create a div where mocha can add its stuff
4 | const mochaDiv = document.createElement('DIV')
5 | mochaDiv.id = 'mocha'
6 | document.body.appendChild(mochaDiv)
7 |
8 | import 'mocha/mocha.js'
9 | import sinon from 'sinon'
10 | import chai from 'chai'
11 | window.mocha.setup({
12 | ui: 'bdd',
13 | slow: 750,
14 | timeout: 5000,
15 | globals: [
16 | '__VUE_DEVTOOLS_INSTANCE_MAP__',
17 | 'script',
18 | 'inject',
19 | 'originalOpenFunction'
20 | ]
21 | })
22 | window.sinon = sinon
23 | chai.use(require('chai-dom'))
24 | chai.use(require('sinon-chai'))
25 | chai.should()
26 |
27 | let vms = []
28 | let testId = 0
29 |
30 | beforeEach(function () {
31 | this.DOMElement = document.createElement('DIV')
32 | this.DOMElement.id = `test-${++testId}`
33 | document.body.appendChild(this.DOMElement)
34 | })
35 |
36 | afterEach(function () {
37 | const testReportElements = document.getElementsByClassName('test')
38 | const lastReportElement = testReportElements[testReportElements.length - 1]
39 |
40 | if (!lastReportElement) return
41 | const el = document.getElementById(this.DOMElement.id)
42 | if (el) lastReportElement.appendChild(el)
43 | // Save the vm to hide it later
44 | if (this.DOMElement.vm) vms.push(this.DOMElement.vm)
45 | })
46 |
47 | // Hide all tests at the end to prevent some weird bugs
48 | before(function () {
49 | vms = []
50 | testId = 0
51 | })
52 | after(function () {
53 | requestAnimationFrame(function () {
54 | setTimeout(function () {
55 | vms.forEach(vm => {
56 | // Hide if test passed
57 | if (!vm.$el.parentElement.classList.contains('fail')) {
58 | vm.$children[0].visible = false
59 | }
60 | })
61 | }, 100)
62 | })
63 | })
64 |
65 | const specsContext = require.context('./specs', true)
66 | specsContext.keys().forEach(specsContext)
67 |
68 | window.mocha.checkLeaks()
69 | window.mocha.run()
70 |
--------------------------------------------------------------------------------