diff --git a/mocha-client-tests/.gitignore b/mocha-client-tests/.gitignore
new file mode 100644
index 0000000..f53b1be
--- /dev/null
+++ b/mocha-client-tests/.gitignore
@@ -0,0 +1,4 @@
+node_modules
+addon/bower_components
+addon/node_modules
+.idea
\ No newline at end of file
diff --git a/mocha-client-tests/README.md b/mocha-client-tests/README.md
new file mode 100644
index 0000000..1d0d376
--- /dev/null
+++ b/mocha-client-tests/README.md
@@ -0,0 +1,17 @@
+#Mocha client tests for WebExtensions
+##Install dependency
+`npm install` - will install all dependencies for running PhantomJS outside of addon
+
+Than please run `cd ./addon/` and `npm install` to install mocha. It give you possibility to run client test inside of addon with mocha UI. If you don't want to have mocha UI you can install [WebConsole-reporter](https://github.com/eeroan/WebConsole-reporter)
+
+##Run with web-ext cli
+Just run `npm run web-ext` (will work with FF dev edition), if you have error with web-ext cli please add path for FF binary file with `--firefox-binary /path/to/firefox-bin`
+[(web-ext docs)](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/web-ext_command_reference).
+
+When addon will start click on mocha icon in your browser bar to run client tests:
+
+
+
+Addon will run test of `./addon/background.js` in `./addon/tests/lib/background-messaging.test.js`
+##PhantomJs tests
+`npm test` will run simple test of `./addon/background.js` in `./tests/lib/background.test.js`
\ No newline at end of file
diff --git a/mocha-client-tests/addon/background.js b/mocha-client-tests/addon/background.js
new file mode 100644
index 0000000..1bda77c
--- /dev/null
+++ b/mocha-client-tests/addon/background.js
@@ -0,0 +1,15 @@
+var Background = {
+ receiveMessage: function(msg, sender, sendResponse) {
+ if (msg && msg.action && Background.hasOwnProperty(msg.action)) {
+ return Background[msg.action](msg, sender, sendResponse);
+ } else {
+ console.warning('No handler for message: ' + JSON.stringify(msg));
+ }
+ },
+ ping: function(msg, sender, sendResponse) {
+ sendResponse('pong');
+ return true;
+ }
+};
+
+chrome.runtime.onMessage.addListener(Background.receiveMessage);
\ No newline at end of file
diff --git a/mocha-client-tests/addon/images/icon-16.png b/mocha-client-tests/addon/images/icon-16.png
new file mode 100644
index 0000000..01da9f4
Binary files /dev/null and b/mocha-client-tests/addon/images/icon-16.png differ
diff --git a/mocha-client-tests/addon/images/icon-19.png b/mocha-client-tests/addon/images/icon-19.png
new file mode 100644
index 0000000..1c80c9d
Binary files /dev/null and b/mocha-client-tests/addon/images/icon-19.png differ
diff --git a/mocha-client-tests/addon/images/mocha.png b/mocha-client-tests/addon/images/mocha.png
new file mode 100644
index 0000000..83f1196
Binary files /dev/null and b/mocha-client-tests/addon/images/mocha.png differ
diff --git a/mocha-client-tests/addon/manifest.json b/mocha-client-tests/addon/manifest.json
new file mode 100644
index 0000000..26b0f43
--- /dev/null
+++ b/mocha-client-tests/addon/manifest.json
@@ -0,0 +1,28 @@
+{
+ "name": "Mocha tests",
+ "version": "1.0",
+ "manifest_version": 2,
+ "description": "Check ",
+ "icons": {
+ "16": "images/icon-16.png"
+ },
+ "short_name": "MochaTest",
+ "background": {
+ "scripts": [
+ "background.js"
+ ]
+ },
+ "browser_action": {
+ "default_icon": {
+ "19": "images/icon-19.png"
+ },
+ "default_title": "Mocha Test",
+ "default_popup": "popup.html"
+ },
+ "applications": {
+ "gecko": {
+ "strict_min_version": "45.0"
+ }
+ },
+ "content_security_policy": "script-src 'self'; object-src 'self'; img-src 'self'"
+}
diff --git a/mocha-client-tests/addon/package.json b/mocha-client-tests/addon/package.json
new file mode 100644
index 0000000..31faea1
--- /dev/null
+++ b/mocha-client-tests/addon/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "mocha-tests-webextension",
+ "version": "1.0.0",
+ "description": "Run test inside your addon",
+ "main": "background.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "MPL-2.0",
+ "devDependencies": {
+ "expect.js": "^0.3.1",
+ "mocha": "^3.1.2"
+ }
+}
diff --git a/mocha-client-tests/addon/popup.html b/mocha-client-tests/addon/popup.html
new file mode 100644
index 0000000..9c33a6f
--- /dev/null
+++ b/mocha-client-tests/addon/popup.html
@@ -0,0 +1,25 @@
+
+
+
+ Mocha Tests
+
+
+
+
+
+
+
+
+Hello! Lets play at ping
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mocha-client-tests/addon/scripts/browser-polyfill.min.js b/mocha-client-tests/addon/scripts/browser-polyfill.min.js
new file mode 100644
index 0000000..8c87af7
--- /dev/null
+++ b/mocha-client-tests/addon/scripts/browser-polyfill.min.js
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+if(typeof browser==="undefined"){const wrapAPIs=()=>{const apiMetadata={"alarms":{"clear":{"minArgs":0,"maxArgs":1},"clearAll":{"minArgs":0,"maxArgs":0},"get":{"minArgs":0,"maxArgs":1},"getAll":{"minArgs":0,"maxArgs":0}},"bookmarks":{"create":{"minArgs":1,"maxArgs":1},"export":{"minArgs":0,"maxArgs":0},"get":{"minArgs":1,"maxArgs":1},"getChildren":{"minArgs":1,"maxArgs":1},"getRecent":{"minArgs":1,"maxArgs":1},"getTree":{"minArgs":0,"maxArgs":0},"getSubTree":{"minArgs":1,"maxArgs":1},"import":{"minArgs":0,
+"maxArgs":0},"move":{"minArgs":2,"maxArgs":2},"remove":{"minArgs":1,"maxArgs":1},"removeTree":{"minArgs":1,"maxArgs":1},"search":{"minArgs":1,"maxArgs":1},"update":{"minArgs":2,"maxArgs":2}},"browserAction":{"getBadgeBackgroundColor":{"minArgs":1,"maxArgs":1},"getBadgeText":{"minArgs":1,"maxArgs":1},"getPopup":{"minArgs":1,"maxArgs":1},"getTitle":{"minArgs":1,"maxArgs":1},"setIcon":{"minArgs":1,"maxArgs":1}},"commands":{"getAll":{"minArgs":0,"maxArgs":0}},"contextMenus":{"update":{"minArgs":2,"maxArgs":2},
+"remove":{"minArgs":1,"maxArgs":1},"removeAll":{"minArgs":0,"maxArgs":0}},"cookies":{"get":{"minArgs":1,"maxArgs":1},"getAll":{"minArgs":1,"maxArgs":1},"getAllCookieStores":{"minArgs":0,"maxArgs":0},"remove":{"minArgs":1,"maxArgs":1},"set":{"minArgs":1,"maxArgs":1}},"downloads":{"download":{"minArgs":1,"maxArgs":1},"cancel":{"minArgs":1,"maxArgs":1},"erase":{"minArgs":1,"maxArgs":1},"getFileIcon":{"minArgs":1,"maxArgs":2},"open":{"minArgs":1,"maxArgs":1},"pause":{"minArgs":1,"maxArgs":1},"removeFile":{"minArgs":1,
+"maxArgs":1},"resume":{"minArgs":1,"maxArgs":1},"search":{"minArgs":1,"maxArgs":1},"show":{"minArgs":1,"maxArgs":1}},"extension":{"isAllowedFileSchemeAccess":{"minArgs":0,"maxArgs":0},"isAllowedIncognitoAccess":{"minArgs":0,"maxArgs":0}},"history":{"addUrl":{"minArgs":1,"maxArgs":1},"getVisits":{"minArgs":1,"maxArgs":1},"deleteAll":{"minArgs":0,"maxArgs":0},"deleteRange":{"minArgs":1,"maxArgs":1},"deleteUrl":{"minArgs":1,"maxArgs":1},"search":{"minArgs":1,"maxArgs":1}},"i18n":{"detectLanguage":{"minArgs":1,
+"maxArgs":1},"getAcceptLanguages":{"minArgs":0,"maxArgs":0}},"idle":{"queryState":{"minArgs":1,"maxArgs":1}},"management":{"get":{"minArgs":1,"maxArgs":1},"getAll":{"minArgs":0,"maxArgs":0},"getSelf":{"minArgs":0,"maxArgs":0},"uninstallSelf":{"minArgs":0,"maxArgs":1}},"notifications":{"clear":{"minArgs":1,"maxArgs":1},"create":{"minArgs":1,"maxArgs":2},"getAll":{"minArgs":0,"maxArgs":0},"getPermissionLevel":{"minArgs":0,"maxArgs":0},"update":{"minArgs":2,"maxArgs":2}},"pageAction":{"getPopup":{"minArgs":1,
+"maxArgs":1},"getTitle":{"minArgs":1,"maxArgs":1},"hide":{"minArgs":0,"maxArgs":0},"setIcon":{"minArgs":1,"maxArgs":1},"show":{"minArgs":0,"maxArgs":0}},"runtime":{"getBackgroundPage":{"minArgs":0,"maxArgs":0},"getBrowserInfo":{"minArgs":0,"maxArgs":0},"getPlatformInfo":{"minArgs":0,"maxArgs":0},"openOptionsPage":{"minArgs":0,"maxArgs":0},"requestUpdateCheck":{"minArgs":0,"maxArgs":0},"sendMessage":{"minArgs":1,"maxArgs":3},"sendNativeMessage":{"minArgs":2,"maxArgs":2},"setUninstallURL":{"minArgs":1,
+"maxArgs":1}},"storage":{"local":{"clear":{"minArgs":0,"maxArgs":0},"get":{"minArgs":0,"maxArgs":1},"getBytesInUse":{"minArgs":0,"maxArgs":1},"remove":{"minArgs":1,"maxArgs":1},"set":{"minArgs":1,"maxArgs":1}},"managed":{"get":{"minArgs":0,"maxArgs":1},"getBytesInUse":{"minArgs":0,"maxArgs":1}},"sync":{"clear":{"minArgs":0,"maxArgs":0},"get":{"minArgs":0,"maxArgs":1},"getBytesInUse":{"minArgs":0,"maxArgs":1},"remove":{"minArgs":1,"maxArgs":1},"set":{"minArgs":1,"maxArgs":1}}},"tabs":{"create":{"minArgs":1,
+"maxArgs":1},"captureVisibleTab":{"minArgs":0,"maxArgs":2},"detectLanguage":{"minArgs":0,"maxArgs":1},"duplicate":{"minArgs":1,"maxArgs":1},"executeScript":{"minArgs":1,"maxArgs":2},"get":{"minArgs":1,"maxArgs":1},"getCurrent":{"minArgs":0,"maxArgs":0},"getZoom":{"minArgs":0,"maxArgs":1},"getZoomSettings":{"minArgs":0,"maxArgs":1},"highlight":{"minArgs":1,"maxArgs":1},"insertCSS":{"minArgs":1,"maxArgs":2},"move":{"minArgs":2,"maxArgs":2},"reload":{"minArgs":0,"maxArgs":2},"remove":{"minArgs":1,"maxArgs":1},
+"query":{"minArgs":1,"maxArgs":1},"removeCSS":{"minArgs":1,"maxArgs":2},"sendMessage":{"minArgs":2,"maxArgs":3},"setZoom":{"minArgs":1,"maxArgs":2},"setZoomSettings":{"minArgs":1,"maxArgs":2},"update":{"minArgs":1,"maxArgs":2}},"webNavigation":{"getAllFrames":{"minArgs":1,"maxArgs":1},"getFrame":{"minArgs":1,"maxArgs":1}},"webRequest":{"handlerBehaviorChanged":{"minArgs":0,"maxArgs":0}},"windows":{"create":{"minArgs":0,"maxArgs":1},"get":{"minArgs":1,"maxArgs":2},"getAll":{"minArgs":0,"maxArgs":1},
+"getCurrent":{"minArgs":0,"maxArgs":1},"getLastFocused":{"minArgs":0,"maxArgs":1},"remove":{"minArgs":1,"maxArgs":1},"update":{"minArgs":2,"maxArgs":2}}};class DefaultWeakMap extends WeakMap{constructor(createItem,items=undefined){super(items);this.createItem=createItem}get(key){if(!this.has(key))this.set(key,this.createItem(key));return super.get(key)}}const isThenable=(value)=>{return value&&typeof value==="object"&&typeof value.then==="function"};const makeCallback=(promise)=>{return(...callbackArgs)=>
+{if(chrome.runtime.lastError)promise.reject(chrome.runtime.lastError);else if(callbackArgs.length===1)promise.resolve(callbackArgs[0]);else promise.resolve(callbackArgs)}};const wrapAsyncFunction=(name,metadata)=>{return function asyncFunctionWrapper(target,...args){if(args.lengthmetadata.maxArgs)throw new Error(`Expected at most ${metadata.maxArgs} arguments for ${name}(), got ${args.length}`);
+return new Promise((resolve,reject)=>{target[name](...args,makeCallback({resolve,reject}))})}};const wrapMethod=(target,method,wrapper)=>{return new Proxy(method,{apply(targetMethod,thisObj,args){return wrapper.call(thisObj,target,...args)}})};let hasOwnProperty=Function.call.bind(Object.prototype.hasOwnProperty);const wrapObject=(target,wrappers={},metadata={})=>{let cache=Object.create(null);let handlers={has(target,prop){return prop in target||prop in cache},get(target,prop,receiver){if(prop in
+cache)return cache[prop];if(!(prop in target))return undefined;let value=target[prop];if(typeof value==="function")if(typeof wrappers[prop]==="function")value=wrapMethod(target,target[prop],wrappers[prop]);else if(hasOwnProperty(metadata,prop)){let wrapper=wrapAsyncFunction(prop,metadata[prop]);value=wrapMethod(target,target[prop],wrapper)}else value=value.bind(target);else if(typeof value==="object"&&value!==null&&(hasOwnProperty(wrappers,prop)||hasOwnProperty(metadata,prop)))value=wrapObject(value,
+wrappers[prop],metadata[prop]);else{Object.defineProperty(cache,prop,{configurable:true,enumerable:true,get(){return target[prop]},set(value){target[prop]=value}});return value}cache[prop]=value;return value},set(target,prop,value,receiver){if(prop in cache)cache[prop]=value;else target[prop]=value;return true},defineProperty(target,prop,desc){return Reflect.defineProperty(cache,prop,desc)},deleteProperty(target,prop){return Reflect.deleteProperty(cache,prop)}};return new Proxy(target,handlers)};
+const wrapEvent=(wrapperMap)=>{addListener(target,listener,...args){target.addListener(wrapperMap.get(listener),...args)},hasListener(target,listener){return target.hasListener(wrapperMap.get(listener))},removeListener(target,listener){target.removeListener(wrapperMap.get(listener))}};const onMessageWrappers=new DefaultWeakMap((listener)=>{if(typeof listener!=="function")return listener;return function onMessage(message,sender,sendResponse){let result=listener(message,sender);if(isThenable(result)){result.then(sendResponse,
+(error)=>{console.error(error);sendResponse(error)});return true}else if(result!==undefined)sendResponse(result)}});const staticWrappers={runtime:{onMessage:wrapEvent(onMessageWrappers)}};return wrapObject(chrome,staticWrappers,apiMetadata)};this.browser=wrapAPIs()};
diff --git a/mocha-client-tests/addon/scripts/popup.js b/mocha-client-tests/addon/scripts/popup.js
new file mode 100644
index 0000000..b352b5f
--- /dev/null
+++ b/mocha-client-tests/addon/scripts/popup.js
@@ -0,0 +1,10 @@
+ setInterval(function() {
+ var $game = document.querySelector('#game');
+ if($game.innerText !== 'ping'){
+ $game.innerText = 'ping';
+ } else{
+ chrome.runtime.sendMessage({action: 'ping'},function(response) {
+ $game.innerText = response;
+ });
+ }
+ }, 1000);
diff --git a/mocha-client-tests/addon/tests/lib/background-messaging.test.js b/mocha-client-tests/addon/tests/lib/background-messaging.test.js
new file mode 100644
index 0000000..b91eb5a
--- /dev/null
+++ b/mocha-client-tests/addon/tests/lib/background-messaging.test.js
@@ -0,0 +1,11 @@
+describe('Background', function() {
+ describe('ping', function() {
+ it('should return pong in response', function() {
+ // Return a promise for Mocha using the Firefox browser API instead of chrome.
+ return browser.runtime.sendMessage({action: 'ping'})
+ .then(function(response) {
+ expect(response).to.equal('pong');
+ });
+ });
+ });
+});
\ No newline at end of file
diff --git a/mocha-client-tests/addon/tests/lib/test.array.js b/mocha-client-tests/addon/tests/lib/test.array.js
new file mode 100644
index 0000000..40a5f5e
--- /dev/null
+++ b/mocha-client-tests/addon/tests/lib/test.array.js
@@ -0,0 +1,8 @@
+describe('Array', function() {
+ describe('#indexOf()', function() {
+ it('should return -1 when the value is not present', function() {
+ expect([1,2,3]).to.not.contain(5);
+ expect([1,2,3]).to.not.contain(0);
+ });
+ });
+});
\ No newline at end of file
diff --git a/mocha-client-tests/addon/tests/mocha-run.js b/mocha-client-tests/addon/tests/mocha-run.js
new file mode 100644
index 0000000..ec1be52
--- /dev/null
+++ b/mocha-client-tests/addon/tests/mocha-run.js
@@ -0,0 +1,5 @@
+mocha.checkLeaks();
+// Here we add initial global variables to prevent this error:
+// Error: global leaks detected: AppView, ExtensionOptions, ExtensionView, WebView
+mocha.globals(['AppView', 'ExtensionOptions', 'ExtensionView', 'WebView']);
+mocha.run();
\ No newline at end of file
diff --git a/mocha-client-tests/addon/tests/mocha-setup.js b/mocha-client-tests/addon/tests/mocha-setup.js
new file mode 100644
index 0000000..58a9e9e
--- /dev/null
+++ b/mocha-client-tests/addon/tests/mocha-setup.js
@@ -0,0 +1 @@
+mocha.setup('bdd');
\ No newline at end of file
diff --git a/mocha-client-tests/package.json b/mocha-client-tests/package.json
new file mode 100644
index 0000000..eb0adb2
--- /dev/null
+++ b/mocha-client-tests/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "mocha-test-webextension",
+ "version": "1.0.0",
+ "description": "Example how to run unit tests for WebExtension",
+ "main": "index.js",
+ "scripts": {
+ "test": "mocha-phantomjs -p ./node_modules/phantomjs-prebuilt/bin/phantomjs -R nyan tests/background.html",
+ "web-ext": "web-ext run -s ./addon"
+ },
+ "author": "",
+ "license": "MPL-2.0",
+ "devDependencies": {
+ "chai": "^3.5.0",
+ "chrome-mock": "0.0.9",
+ "mocha": "^3.1.2",
+ "mocha-phantomjs": "^4.1.0",
+ "phantomjs-prebuilt": "^2.1.13",
+ "expect.js": "^0.3.1",
+ "web-ext": "^1.6.0"
+ }
+}
diff --git a/mocha-client-tests/screenshots/addon-button.png b/mocha-client-tests/screenshots/addon-button.png
new file mode 100644
index 0000000..6172e83
Binary files /dev/null and b/mocha-client-tests/screenshots/addon-button.png differ
diff --git a/mocha-client-tests/tests/background.html b/mocha-client-tests/tests/background.html
new file mode 100644
index 0000000..b2993b1
--- /dev/null
+++ b/mocha-client-tests/tests/background.html
@@ -0,0 +1,26 @@
+
+
+
+ Tests for background
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mocha-client-tests/tests/lib/background.test.js b/mocha-client-tests/tests/lib/background.test.js
new file mode 100644
index 0000000..bf42c31
--- /dev/null
+++ b/mocha-client-tests/tests/lib/background.test.js
@@ -0,0 +1,9 @@
+describe('Background', function() {
+ describe('ping', function() {
+ it('should return pong in response', function() {
+ Background.ping(false, false, function(response) {
+ expect(response).to.equal('pong');
+ });
+ });
+ });
+});
\ No newline at end of file