diff --git a/.gitignore b/.gitignore index 7a1537b..a519eb0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea node_modules +npm_debug.log \ No newline at end of file diff --git a/LICENSE b/LICENSE index e83e7c4..f1c3504 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Maslov Ivan +Copyright (c) 2015 Maslov Ivan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 1b12b01..dbd2897 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,19 @@ -DuelJS v1.1.0 +DuelJS v1.2.0 ====== +[![Documentation Status](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat-square)](http://dueljs.readthedocs.org/) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/studentIvan/dueljs/master/LICENSE) [![Bower](https://img.shields.io/bower/v/duel.svg?style=flat-square)](http://bower.io/search/?q=duel) [![](https://img.shields.io/github/issues-raw/studentIvan/dueljs.svg?style=flat-square)](https://github.com/studentIvan/dueljs/issues/) [![GitHub stars](https://img.shields.io/github/stars/studentIvan/dueljs.svg?style=flat-square)](https://github.com/studentIvan/dueljs/stargazers) + JavaScript HTML5 Master/Slave Browser Tabs Helper. -See a brief documentation on the [the homepage](http://dueljs.studentivan.ru) +See a brief look on [the homepage](http://dueljs.studentivan.ru) + +Documentation available on http://dueljs.readthedocs.org/ + +######New in 1.2.0: +* New method: channel.off - stop watching event +* New method: channel.once - executing callback only one time and stop watching event +* New method: channel.emit - the alias of channel.broadcast +* Function window.isMaster() now returns true even if no one channel has initialized [#3](https://github.com/studentIvan/dueljs/issues/3) +* Dev test coverage (Mocha + PhantomJS) ######New in 1.1.0: * "storage" event improves performance in modern browsers. diff --git a/bower.json b/bower.json index 1241d54..a285583 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "duel", - "version": "1.1.0", + "version": "1.2.0", "homepage": "http://dueljs.studentivan.ru", "authors": [ "Maslov Ivan " @@ -21,6 +21,8 @@ "license": "MIT", "ignore": [ "node_modules", - "bower_components" + "bower_components", + "docs", + "test" ] } diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..fbc1b00 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,126 @@ +API:duel +======== + +duel +---- +Main global object. + +* global +* type: object + +duel.activeChannels +------------------- +Collect inside all active channels. + +* public +* type: array +* default: [] + +duel.useStorageEvent +-------------------- +Optional configuration. Storage event improves performance in modern browsers. + +* public +* type: boolean +* default: true (IE - false) + +duel.isLocalStorageAvailable() +------------------------------ +Common function for localStorage detection. + +* public +* type: function +* returns: boolean + +duel.channel(name:string) +------------------------- +Creates a new channel or join to existed. +Hint: a = duel.channel('x') and b = duel.channel('x') will be linking on ONE object. + +* public +* type: function +* returns: object (duel.DuelAbstractChannel inheritor) + +duel.makeCurrentWindowMaster() +------------------------------ +Take the all channels in current window and set current window as master for all of them. + +* public +* type: function + +duel.clone(obj:object) +---------------------- +Common function for copy objects. + +* public +* type: function +* returns: object +* throws error on unsupported type + +duel._generateWindowID() +------------------------ +Generates, sets up and returns new window/tab ID. + +* private +* type: function +* returns: number + +duel.getWindowID() +------------------ +Get unique window/tab ID. + +* public +* type: function +* returns: number + +duel.addEvent(el:object, type:string, fn:function) +-------------------------------------------------- +Cross-browser addEvent method. + +* public +* type: function + +duel.storageEvent(event:object) +------------------------------- +Finds the specific channel and execute event on it. **event** object contains **key:string** and **newValue:string**. +**newValue** is a JSON string, which contains **channelName:string** and **triggerDetails:object**. +**triggerDetails** contains **name:string** and **args:array**. + +* public +* type: function + +duel.DuelAbstractChannel +------------------------ +Abstract class for possible duel channels. DuelJS probably will support another channels besides **duel.DuelLocalStorageChannel** in future. + +* abstract +* type: function +* returns: object + +duel.DuelLocalStorageChannel +---------------------------- +Channel class for work with localStorage. + +* abstract +* type: function +* returns: object + +duel.DuelFakeChannel +-------------------- +Channel class for work without localStorage. + +* abstract +* type: function +* returns: object + +window.isMaster() +----------------- +Take first channel in current window and check is it master or not + +Standard window object spreading method. +Looks like syntax sugar for channelObject.currentWindowIsMaster() + +* public +* type: function +* returns: boolean + diff --git a/docs/channel_api.rst b/docs/channel_api.rst new file mode 100644 index 0000000..c3af36e --- /dev/null +++ b/docs/channel_api.rst @@ -0,0 +1,74 @@ +API:channel object interface +============================ + +channel.getName() +----------------- +Returns the name of this channel. + +* public +* type: function +* returns: string + +channel.setCurrentWindowAsMaster() +---------------------------------- +Makes current window/tab as master in this channel. + +* public +* type: function +* returns: boolean + +channel.currentWindowIsMaster() +------------------------------- +Checks the master state of this channel. + +* public +* type: function +* returns: boolean + +channel.broadcast(trigger:string[, arguments:arguments]) +-------------------------------------------------------- +Emits broadcasting. Hint: only master can sends broadcast. + +* public +* type: function + +channel.emit(trigger:string[, arguments:arguments]) +--------------------------------------------------- +Alias of **channel.broadcast** + +channel.executeTrigger(triggerDetails:object[, force:boolean]) +-------------------------------------------------------------- +Executes pointed trigger. **triggerDetails** contains **name:string** and **args:array** + +* public +* type: function +* throws error if triggerDetails is not an instance of Object + +channel.on(trigger:string, callback:function) +--------------------------------------------- +Attaches callback to trigger event. + +* public +* type: function + +channel.once(trigger:string, callback:function) +----------------------------------------------- +Attaches callback to trigger event only for one time. + +* public +* type: function + +channel.off(trigger:string) +--------------------------- +Detaches callback from trigger event (destroys trigger). + +* public +* type: function + +channel._triggers +----------------- +Contains triggers of this channel. + +* private +* type: object + diff --git a/docs/getting_started.rst b/docs/getting_started.rst new file mode 100644 index 0000000..c428f02 --- /dev/null +++ b/docs/getting_started.rst @@ -0,0 +1,71 @@ +Getting Started +=============== + +Getting started with DuelJS. + +Requirements +------------ + +With DuelJS you no need to any requirements - only vanilla js and modern web browser. +Don't try to use it with node.js without browser emulators. + +Installing DuelJS +----------------- + +You can install it using bower or simple copy duel.js main file into your site or even clone git repository. + +Different ways: + +* Bower package: ``bower install duel --save`` +* Git repo: ``git clone https://github.com/studentIvan/dueljs.git`` +* Main file: `duel.min.js `_ + + +Put it into your webpage: +```` + +So we've got all the set up out of the way. Let's write some simple code. +:: + + +Broadcasting +------------ + +When your tab had some channel (e.g. **my_first_channel** from previous section) you can do cross-tab broadcasting. + +Use simple commands: + +* **channel.broadcast('event_name', a,r,g,u,m,e,n,t,s...);** + - send event command to all another tabs in channel. Alias: **channel.emit**. +* **channel.on('event_name', function (a,r,g,u,m,e,n,t,s...) { do here what you want });** + - define watcher for event_name. +* **channel.off('event_name');** + - remove event_name watcher. +* **channel.once('event_name', function (a,r,g,u,m,e,n,t,s...) { do here what you want });** + - define watcher for event_name, do it only one time and remove. + +Articles and examples +--------------------- + +* `Задача коммуникации между вкладками и выявления активной вкладки `_ (russian) +* `Youtube-player integration demonstration `_ (russian) + +If you still need more docs go to `API`_ section + +.. _API: api.html \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..7d6b6f8 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,18 @@ +DuelJS Documentation +==================== + +.. note:: Documentation still in progress. +Welcome to the DuelJS JavaScript library documentation. + +.. _site-docs: + +User Documentation +------------------ + +.. toctree:: + :maxdepth: 2 + + getting_started + api + channel_api + diff --git a/package.json b/package.json index 6d536a1..c922221 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,17 @@ { "name": "duel", - "version": "1.1.0", + "version": "1.2.0", "description": "DuelJS - JavaScript HTML5 Master/Slave Browser Tabs Helper", "main": "public/lib/duel.js", "devDependencies": { - "http-server": "^0.6.1" + "chai": "^1.10.0", + "mocha": "^2.1.0", + "mocha-phantomjs": "^3.5.3", + "phantomjs": "^1.9.13" }, "scripts": { - "pretest": "npm install", - "test": "http-server -a 0.0.0.0 -p 8624 -c-1" + "pretest": "npm install && mocha-phantomjs test/mocha.test.html", + "test": "phantomjs test/phantom.script.js" }, "repository": { "type": "git", diff --git a/public/index.html b/public/index.html index b69861e..3345bb1 100644 --- a/public/index.html +++ b/public/index.html @@ -5,7 +5,7 @@ Duel page - +
diff --git a/public/lib/duel.js b/public/lib/duel.js index 0d262b8..b6ed762 100644 --- a/public/lib/duel.js +++ b/public/lib/duel.js @@ -1,6 +1,7 @@ /*! - * DuelJS JavaScript Library v1.1.0 + * DuelJS JavaScript Library v1.2.0 * https://github.com/studentIvan/dueljs + * http://dueljs.readthedocs.org/en/latest/ * * Copyright 2015 Maslov Ivan * Released under the MIT license @@ -85,9 +86,10 @@ duel.DuelAbstractChannel = (function () { /** * Only master can sends broadcast + * @param {string} trigger */ DuelAbstractChannel.prototype.broadcast = function (trigger) { - if (window.isMaster()) { + if (this.currentWindowIsMaster()) { this.executeTrigger({ name: trigger, args: Array.prototype.slice.call(arguments, 1) @@ -95,22 +97,39 @@ duel.DuelAbstractChannel = (function () { } }; + /** + * Alias of broadcast + * @param {string} trigger + */ + DuelAbstractChannel.prototype.emit = function (trigger) { + this.broadcast(trigger); + }; + /** * * @param {{name: string, args: []}} triggerDetails + * @param {boolean} force */ - DuelAbstractChannel.prototype.executeTrigger = function (triggerDetails) { - if (!window.isMaster()) { + DuelAbstractChannel.prototype.executeTrigger = function (triggerDetails, force) { + force = force || false; + if (!(triggerDetails instanceof Object)) { + throw "triggerDetails should be an Object"; + } + if (!this.currentWindowIsMaster() || force) { try { - this._triggers[triggerDetails.name].apply(this, triggerDetails.args); - } catch (e) { - } + if (this._triggers[triggerDetails.name] instanceof Array) { + this._triggers[triggerDetails.name][0].apply(this, triggerDetails.args); + delete this._triggers[triggerDetails.name]; + } else { + this._triggers[triggerDetails.name].apply(this, triggerDetails.args); + } + } catch (e) {} } }; /** * @param {string} trigger - * @param {function} callback + * @param {function|[]} callback */ DuelAbstractChannel.prototype.on = function (trigger, callback) { if (!this._triggers) { @@ -124,6 +143,21 @@ duel.DuelAbstractChannel = (function () { this._triggers[trigger] = callback; }; + /** + * @param {string} trigger + * @param {function} callback + */ + DuelAbstractChannel.prototype.once = function (trigger, callback) { + this.on(trigger, [callback]); + }; + + /** + * @param {string} trigger + */ + DuelAbstractChannel.prototype.off = function (trigger) { + try { delete this._triggers[trigger]; } catch (e) {} + }; + return DuelAbstractChannel })(); @@ -213,7 +247,7 @@ duel.DuelLocalStorageChannel.prototype.currentWindowIsMaster = function () { * Only master can sends broadcast */ duel.DuelLocalStorageChannel.prototype.broadcast = function (trigger) { - if (window.isMaster()) { + if (this.currentWindowIsMaster()) { // broadcast new task localStorage.setItem('dueljs_trigger', JSON.stringify({ channelName: this.getName(), @@ -258,6 +292,12 @@ duel.activeChannels = []; * @returns {duel.DuelLocalStorageChannel} */ duel.channel = function (name) { + for (var chID in duel.activeChannels) { + if (duel.activeChannels.hasOwnProperty(chID) && + duel.activeChannels[chID].getName() == name) { + return duel.activeChannels[chID]; + } + } var channel = this.isLocalStorageAvailable() ? new this.DuelLocalStorageChannel(name) : new this.DuelFakeChannel(name); duel.activeChannels.push(channel); @@ -291,7 +331,7 @@ window.onfocus = function () { */ window.isMaster = function () { return duel.activeChannels.length ? - duel.activeChannels[0].currentWindowIsMaster() : false; + duel.activeChannels[0].currentWindowIsMaster() : true; }; /** diff --git a/public/lib/duel.min.js b/public/lib/duel.min.js index 6b0de9e..9ea13ba 100644 --- a/public/lib/duel.min.js +++ b/public/lib/duel.min.js @@ -1,10 +1,11 @@ /*! - * DuelJS JavaScript Library v1.1.0 + * DuelJS JavaScript Library v1.2.0 * https://github.com/studentIvan/dueljs + * http://dueljs.readthedocs.org/en/latest/ * * Copyright 2014 Maslov Ivan * Released under the MIT license * https://raw.githubusercontent.com/studentIvan/dueljs/master/LICENSE */ -duel={};duel.useStorageEvent=!/trident|MSIE/i.test(navigator.userAgent);duel.isLocalStorageAvailable=function(){try{return"localStorage"in window&&window["localStorage"]!==null&&window["localStorage"]!==undefined}catch(e){return false}};duel.clone=function(e){var t;if(null==e||"object"!=typeof e)return e;if(e instanceof Object){t={};for(var n in e){if(e.hasOwnProperty(n))t[n]=duel.clone(e[n])}return t}throw new Error("Unable to copy obj! Its type isn't supported.")};duel.DuelAbstractChannel=function(){var e=function(){};e.prototype.getName=function(){return this._name};e.prototype.setCurrentWindowAsMaster=function(){return true};e.prototype.currentWindowIsMaster=function(){return true};e.prototype.broadcast=function(e){if(window.isMaster()){this.executeTrigger({name:e,args:Array.prototype.slice.call(arguments,1)})}};e.prototype.executeTrigger=function(e){if(!window.isMaster()){try{this._triggers[e.name].apply(this,e.args)}catch(t){}}};e.prototype.on=function(e,t){if(!this._triggers){this._triggers={}}this._triggers[e]=t};return e}();duel._generateWindowID=function(){this._windowID=+Math.random().toString().split(".")[1];return this._windowID};duel.getWindowID=function(){return this._windowID?this._windowID:this._generateWindowID()};duel.DuelLocalStorageChannel=function(e){this._name=e;this.setCurrentWindowAsMaster()};duel.DuelLocalStorageChannel.prototype=duel.clone(duel.DuelAbstractChannel.prototype);duel.DuelLocalStorageChannel.prototype.setCurrentWindowAsMaster=function(){var e,t,n,r,i=duel.getWindowID(),s="dueljs_channel_"+this.getName();if(n=localStorage.getItem(s)){for(n=JSON.parse(n),r=-1,e=0,t=n.length;e=0;e--){try{duel.activeChannels[e].setCurrentWindowAsMaster()}catch(t){}}};window.onfocus=function(){duel.makeCurrentWindowMaster()};window.isMaster=function(){return duel.activeChannels.length?duel.activeChannels[0].currentWindowIsMaster():false};duel.addEvent=function(){if(document.addEventListener){return function(e,t,n){if(e&&e.nodeName||e===window){e.addEventListener(t,n,false)}else if(e&&e.length){for(var r=0;r=0;n--){try{if(duel.activeChannels[n].getName()==t.channelName){duel.activeChannels[n].executeTrigger(t.triggerDetails)}}catch(r){}}}};if(duel.isLocalStorageAvailable()){duel.storageOldTriggerValue=localStorage.getItem("dueljs_trigger_event_key");if(duel.useStorageEvent){duel.addEvent(window,"storage",function(e){var t=e||t||window.event;if(t.key=="dueljs_trigger_event_key"&&t.newValue!=duel.storageOldTriggerValue){duel.storageOldTriggerValue=localStorage.getItem("dueljs_trigger_event_key");duel.storageEvent({key:"dueljs_trigger",newValue:localStorage.getItem("dueljs_trigger")})}})}else{setInterval(function(){if(localStorage.getItem("dueljs_trigger_event_key")!=duel.storageOldTriggerValue){duel.storageOldTriggerValue=localStorage.getItem("dueljs_trigger_event_key");duel.storageEvent({key:"dueljs_trigger",newValue:localStorage.getItem("dueljs_trigger")})}},100)}} \ No newline at end of file +duel={};duel.useStorageEvent=!/trident|MSIE/i.test(navigator.userAgent);duel.isLocalStorageAvailable=function(){try{return"localStorage"in window&&window["localStorage"]!==null&&window["localStorage"]!==undefined}catch(e){return false}};duel.clone=function(e){var t;if(null==e||"object"!=typeof e)return e;if(e instanceof Object){t={};for(var n in e){if(e.hasOwnProperty(n))t[n]=duel.clone(e[n])}return t}throw new Error("Unable to copy obj! Its type isn't supported.")};duel.DuelAbstractChannel=function(){var e=function(){};e.prototype.getName=function(){return this._name};e.prototype.setCurrentWindowAsMaster=function(){return true};e.prototype.currentWindowIsMaster=function(){return true};e.prototype.broadcast=function(e){if(this.currentWindowIsMaster()){this.executeTrigger({name:e,args:Array.prototype.slice.call(arguments,1)})}};e.prototype.emit=function(e){this.broadcast(e)};e.prototype.executeTrigger=function(e,t){t=t||false;if(!(e instanceof Object)){throw"triggerDetails should be an Object"}if(!this.currentWindowIsMaster()||t){try{if(this._triggers[e.name]instanceof Array){this._triggers[e.name][0].apply(this,e.args);delete this._triggers[e.name]}else{this._triggers[e.name].apply(this,e.args)}}catch(n){}}};e.prototype.on=function(e,t){if(!this._triggers){this._triggers={}}this._triggers[e]=t};e.prototype.once=function(e,t){this.on(e,[t])};e.prototype.off=function(e){try{delete this._triggers[e]}catch(t){}};return e}();duel._generateWindowID=function(){this._windowID=+Math.random().toString().split(".")[1];return this._windowID};duel.getWindowID=function(){return this._windowID?this._windowID:this._generateWindowID()};duel.DuelLocalStorageChannel=function(e){this._name=e;this.setCurrentWindowAsMaster()};duel.DuelLocalStorageChannel.prototype=duel.clone(duel.DuelAbstractChannel.prototype);duel.DuelLocalStorageChannel.prototype.setCurrentWindowAsMaster=function(){var e,t,n,r,i=duel.getWindowID(),s="dueljs_channel_"+this.getName();if(n=localStorage.getItem(s)){for(n=JSON.parse(n),r=-1,e=0,t=n.length;e=0;e--){try{duel.activeChannels[e].setCurrentWindowAsMaster()}catch(t){}}};window.onfocus=function(){duel.makeCurrentWindowMaster()};window.isMaster=function(){return duel.activeChannels.length?duel.activeChannels[0].currentWindowIsMaster():true};duel.addEvent=function(){if(document.addEventListener){return function(e,t,n){if(e&&e.nodeName||e===window){e.addEventListener(t,n,false)}else if(e&&e.length){for(var r=0;r=0;n--){try{if(duel.activeChannels[n].getName()==t.channelName){duel.activeChannels[n].executeTrigger(t.triggerDetails)}}catch(r){}}}};if(duel.isLocalStorageAvailable()){duel.storageOldTriggerValue=localStorage.getItem("dueljs_trigger_event_key");if(duel.useStorageEvent){duel.addEvent(window,"storage",function(e){var t=e||t||window.event;if(t.key=="dueljs_trigger_event_key"&&t.newValue!=duel.storageOldTriggerValue){duel.storageOldTriggerValue=localStorage.getItem("dueljs_trigger_event_key");duel.storageEvent({key:"dueljs_trigger",newValue:localStorage.getItem("dueljs_trigger")})}})}else{setInterval(function(){if(localStorage.getItem("dueljs_trigger_event_key")!=duel.storageOldTriggerValue){duel.storageOldTriggerValue=localStorage.getItem("dueljs_trigger_event_key");duel.storageEvent({key:"dueljs_trigger",newValue:localStorage.getItem("dueljs_trigger")})}},100)}} \ No newline at end of file diff --git a/test/duel_test.js b/test/duel_test.js new file mode 100644 index 0000000..2c32513 --- /dev/null +++ b/test/duel_test.js @@ -0,0 +1,175 @@ +var expect = chai.expect; + +Object.size = function (obj) { + var size = 0, key; + for (key in obj) { + if (obj.hasOwnProperty(key)) size++; + } + return size; +}; + +describe('DuelJS test case 1', function () { + describe('#initialization testing', function () { + + it('duel should be initialized', function () { + expect(duel).to.be.an.instanceof(Object); + }); + + if (!/trident|MSIE/i.test(navigator.userAgent)) { + it('duel should be with useStorageEvent == true with ' + navigator.userAgent, function () { + expect(duel.useStorageEvent).to.equal(true); + }); + } else { + it('duel should be with useStorageEvent == false with ' + navigator.userAgent, function () { + expect(duel.useStorageEvent).to.equal(false); + }); + } + + it('duel.isLocalStorageAvailable should be a function and should returns true', function () { + expect(duel.isLocalStorageAvailable).not.to.be.an('undefined'); + expect(duel.isLocalStorageAvailable()).to.equal(true); + }); + + it('duel integrity should be complete', function () { + expect(duel.clone).not.to.be.an('undefined'); + expect(duel.DuelAbstractChannel).not.to.be.an('undefined'); + expect(duel.getWindowID).not.to.be.an('undefined'); + expect(duel.DuelLocalStorageChannel).not.to.be.an('undefined'); + expect(duel.DuelLocalStorageChannel.prototype.getName).not.to.be.an('undefined'); + expect(duel.DuelLocalStorageChannel.prototype.executeTrigger).not.to.be.an('undefined'); + expect(duel.DuelLocalStorageChannel.prototype.on).not.to.be.an('undefined'); + expect(duel.DuelLocalStorageChannel.prototype.once).not.to.be.an('undefined'); + expect(duel.DuelLocalStorageChannel.prototype.off).not.to.be.an('undefined'); + expect(duel.DuelLocalStorageChannel.prototype.emit).not.to.be.an('undefined'); + expect(duel.DuelLocalStorageChannel.prototype.setCurrentWindowAsMaster).not.to.be.an('undefined'); + expect(duel.DuelLocalStorageChannel.prototype.currentWindowIsMaster).not.to.be.an('undefined'); + expect(duel.DuelLocalStorageChannel.prototype.broadcast).not.to.be.an('undefined'); + expect(duel.DuelFakeChannel).not.to.be.an('undefined'); + expect(duel.DuelFakeChannel.prototype.getName).not.to.be.an('undefined'); + expect(duel.DuelFakeChannel.prototype.executeTrigger).not.to.be.an('undefined'); + expect(duel.DuelFakeChannel.prototype.on).not.to.be.an('undefined'); + expect(duel.DuelFakeChannel.prototype.once).not.to.be.an('undefined'); + expect(duel.DuelFakeChannel.prototype.off).not.to.be.an('undefined'); + expect(duel.DuelFakeChannel.prototype.emit).not.to.be.an('undefined'); + expect(duel.DuelFakeChannel.prototype.setCurrentWindowAsMaster).not.to.be.an('undefined'); + expect(duel.DuelFakeChannel.prototype.currentWindowIsMaster).not.to.be.an('undefined'); + expect(duel.DuelFakeChannel.prototype.broadcast).not.to.be.an('undefined'); + expect(duel.activeChannels).to.be.an('array'); + expect(duel.channel).not.to.be.an('undefined'); + expect(duel.makeCurrentWindowMaster).not.to.be.an('undefined'); + expect(window.isMaster).not.to.be.an('undefined'); + expect(duel.addEvent).not.to.be.an('undefined'); + expect(duel.storageEvent).not.to.be.an('undefined'); + }); + }); + + describe('#window testing', function () { + + duel.activeChannels = []; + + it('duel.getWindowID should returns unique Window ID', function () { + var windowID = duel.getWindowID(); + expect(windowID).to.be.a('number'); + expect(duel.getWindowID()).to.equal(windowID); + }); + + it('window.isMaster() should be equal true without channels', function () { + expect(window.isMaster()).to.equal(true); + + describe('DuelJS test case 2', function () { + describe('#channel testing', function () { + var channel = duel.channel('_test'); + + it('duel.channel should returns new channel instance', function () { + expect(channel).to.be.an.instanceof(Object); + }); + + it('window.isMaster() should be equal true with initialized channel', function () { + expect(window.isMaster()).to.equal(true); + }); + + it('should not to dublicate an channels', function () { + channel = duel.channel('_test'); + channel = duel.channel('_test'); + var channel2 = duel.channel('_test2'); + var channel3 = duel.channel('_test'); + expect(duel.activeChannels.length).to.equal(2); + expect(channel3).to.equal(channel); + expect(channel.currentWindowIsMaster()).to.equal(true); + expect(channel2.currentWindowIsMaster()).to.equal(true); + expect(channel3.currentWindowIsMaster()).to.equal(true); + }); + + it('should create the event', function (done) { + var _check = -5; + channel.on('test_event', function (a, b, c) { + _check += a + b + c; + expect(_check).to.equal(0); + }); + channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}, true); + expect(channel._triggers).not.to.eql({}); + channel._triggers = {}; + done(); + }); + + it('should remove the event', function (done) { + var _check = -5; + channel.on('test_event', function (a, b, c) { + _check += a + b + c; + }); + channel.off('test_event'); + channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}, true); + expect(_check).to.equal(-5); + expect(channel._triggers).to.eql({}); + done(); + }); + + it('should create the event and remove it after once', function (done) { + var __check = -5; + expect(channel._triggers).to.eql({}); + channel.once('test_event', function (a, b, c) { + __check += a + b + c; + }); + expect(channel._triggers).not.to.eql({}); + channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}, true); + expect(__check).to.equal(0); + channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}, true); + expect(__check).to.equal(0); + channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}, true); + channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}, true); + channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}, true); + expect(__check).to.equal(0); + expect(channel._triggers).to.eql({}); + channel.off('test_event'); + done(); + }); + + it('should not execute an trigger in a master window', function () { + var __check = -5; + channel.once('test_event', function (a, b, c) { + __check += a + b + c; + }); + channel.executeTrigger({name: 'test_event', args: [0, 4, 1]}); + expect(__check).to.equal(-5); + channel.off('test_event'); + }); + + it('should not to dublicate an triggers', function () { + expect(channel._triggers).to.eql({}); + channel.on('test_event', function () { + return true; + }); + expect(channel._triggers).not.to.eql({}); + expect(Object.size(channel._triggers)).to.eql(1); + channel.on('test_event', function () { + return true; + }); + expect(Object.size(channel._triggers)).to.eql(1); + channel.off('test_event'); + expect(Object.size(channel._triggers)).to.eql(0); + }); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/mocha.test.html b/test/mocha.test.html new file mode 100644 index 0000000..06e6810 --- /dev/null +++ b/test/mocha.test.html @@ -0,0 +1,26 @@ + + + + + + + +
+ + + + + + + + \ No newline at end of file diff --git a/test/phantom.script.js b/test/phantom.script.js new file mode 100644 index 0000000..7135875 --- /dev/null +++ b/test/phantom.script.js @@ -0,0 +1,59 @@ +/** + * PhantomJS Test Script + */ + +var webPage = require('webpage'); + +var page1 = webPage.create(); +var page2 = webPage.create(); +var page3 = webPage.create(); + +function shouldBeEqual(arg1, arg2) { + if (arg1 === arg2) { + console.log(arg1, '===', arg2, 'ok'); + } else { + console.log(arg1, '!==', arg2, 'test failed'); + phantom.exit(); + } +} + +page1.open('test/phantom.tab.html', function () { + + var title1 = page1.evaluate(function () { + return document.title; + }); + + shouldBeEqual(/master/i.test(title1), true); + + page2.open('test/phantom.tab.html', function () { + + page3.open('test/phantom.tab.html', function () { + + var wndID1 = page1.evaluate(function () { + return duel.getWindowID(); + }); + + page2.onCallback = function (data) { + shouldBeEqual(data.wndID, wndID1.toString() + ' "ya"'); + }; + + page3.onCallback = function (data) { + shouldBeEqual(data.wndID, wndID1.toString() + ' "ya"'); + }; + + var broadcast = page1.evaluate(function () { + channel.broadcast('demo_trigger', 'ya', duel.getWindowID()); + return true; + }); + + shouldBeEqual(broadcast, true); + + /** + * Why storage event doesn't work in phantom? + * https://github.com/ariya/phantomjs/issues/12879 + */ + + setTimeout(phantom.exit, 2000); + }); + }); +}); \ No newline at end of file diff --git a/test/phantom.tab.html b/test/phantom.tab.html new file mode 100644 index 0000000..019509b --- /dev/null +++ b/test/phantom.tab.html @@ -0,0 +1,34 @@ + + + + + Duel page + + + + +
+

Window ...

+
Open new window  +
+
+ Hint: open the developer console before broadcasting. +
+
+ + + \ No newline at end of file