mirror of
https://github.com/gundb/panic-server.git
synced 2026-05-07 03:00:26 -04:00
Rename .len() to .length, allow subclassing, export client bundle.
Instead of calling a method to find the length of a list, you can use a property (which is a getter under the hood, doing the same thing as `.len()`). This is cleaner and more intuitive, aligning itself more with arrays. Subclassing is now facilitated by a new method, `.chain`. It creates a new list instance by calling the constructor property, instead of statically creating a new ClientList instance. This allows you to create subclasses that inherit from ClientList, without losing that inheritance when calling `.filter` or `.pluck` (methods which create new list instances). The client bundle is now exported lazily, so when you import panic, there's a `client` getter which memoizes a fs call for the client code. This allows compatibility on pre-3.0 versions of npm, where other packages might not be able to recursively find `panic-client`. Also, since it's a getter, it doesn't do the file system call until it's needed.
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## v0.3.0
|
||||
Improvements: deprecated `.len()` in favor of `.length`, allow ClientList to be subclassable, and lazily export the client code onto the `panic.client` property.
|
||||
|
||||
Subclassing is curtesy of a new method, `.chain`, which ensures the `this` context's constructor is called instead of statically calling `new ClientList()`.
|
||||
|
||||
## v0.2.4
|
||||
Set the `constructor` property on the ClientList prototype.
|
||||
|
||||
|
||||
34
README.md
34
README.md
@@ -7,9 +7,9 @@
|
||||
[](https://gitter.im/amark/gun)
|
||||
|
||||
> **TL;DR:**<br />
|
||||
It's a glorified `eval()` with platform queries.
|
||||
A lightweight tool for browser and node.js choreography.
|
||||
|
||||
Panic-server is designed as the underlying layer for panic-room, the distributed testing framework. It allows you to dynamically and reactively group connected clients and evaluate code on platform subsets.
|
||||
Panic-server is designed for distributed testing. It allows you to dynamically group clients and control them through Javascript, and is compatible with the test frameworks you already use. Think of it as Selenium WebDriver on steroids.
|
||||
|
||||
For example:
|
||||
```javascript
|
||||
@@ -127,7 +127,10 @@ Once you have a server listening, point browsers/servers to your address ([here'
|
||||
> **Note:** if you're using [PhantomJS](https://github.com/ariya/phantomjs), you'll need to serve the html page over http/s for socket.io to work.
|
||||
|
||||
### `panic.clients`
|
||||
Every group is a ClientList instance, and inherits from EventEmitter. They update in real-time as clients are added and disconnected, and have array-like methods for manipulating and filtering. `panic.clients` is the root level list, and contains every client currently connected.
|
||||
Every group is a ClientList instance, and inherits from EventEmitter. They update in real-time as clients are added and disconnected, and have [RxJS](https://github.com/Reactive-Extensions/RxJS)-style methods for manipulating and filtering. `panic.clients` is the root level list, and contains every client currently connected.
|
||||
|
||||
### `panic.client`
|
||||
Returns the panic-client bundle code. This is useful for injection into a WebDriver instance (using `driver.executeScript`) without needing to do file system calls. The property is immutable and
|
||||
|
||||
#### Events
|
||||
As the list changes, it will emit one of two mutation events:
|
||||
@@ -188,11 +191,12 @@ var client = {
|
||||
- [`.excluding()`](#excluding)
|
||||
- [`.pluck()`](#pluck)
|
||||
- [`.run()`](#run)
|
||||
- [`.len()`](#len)
|
||||
- [`.length`](#length)
|
||||
- [`.get()`](#get)
|
||||
- [`.add()`](#add)
|
||||
- [`.remove()`](#remove)
|
||||
- [`.each()`](#each)
|
||||
- [`.chain()`](#chain)
|
||||
|
||||
##### <a name='filter'></a> `.filter(query)`
|
||||
Returns a filtered list containing everything that matches a platform query.
|
||||
@@ -471,8 +475,8 @@ clients.run(function () {
|
||||
})
|
||||
```
|
||||
|
||||
##### <a name='len'></a> `.len()`
|
||||
Returns the number of clients in a list.
|
||||
##### <a name='length'></a> `.length`
|
||||
A getter property which returns the number of clients in a list.
|
||||
|
||||
|
||||
**Low-level API**
|
||||
@@ -505,6 +509,24 @@ clients.each(function (client, id, list) {
|
||||
})
|
||||
```
|
||||
|
||||
##### <a name='chain'></a> `.chain([...lists])`
|
||||
This is an abstraction method that just calls `this.constructor` to create a new instance. Mainly used to allow subclassing, it makes sure the right class context is kept even when chaining off methods that create new lists, like `.filter` and `.pluck`.
|
||||
|
||||
```javascript
|
||||
var list = new ClientList()
|
||||
list.chain() instanceof ClientList // true
|
||||
|
||||
class SubClass extends ClientList {
|
||||
coolNewMethod() { /* bacon */ }
|
||||
}
|
||||
|
||||
var sub = new SubClass()
|
||||
sub.chain() instanceof SubClass // true
|
||||
sub.chain() instanceof ClientList // true
|
||||
sub.chain().coolNewMethod() // properly inherits
|
||||
```
|
||||
|
||||
If you're making an extension that creates a new list instance, use this method to play nice with other extensions.
|
||||
|
||||
## Support
|
||||
If you have questions or ideas, we'd love to hear them! Just swing by our [gitter channel](https://gitter.im/amark/gun) and ask for @PsychoLlama or @amark. We're usually around :wink:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "panic-server",
|
||||
"version": "0.2.4",
|
||||
"version": "0.3.0",
|
||||
"description": "Distributed Javascript runner",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
var Emitter = require('events');
|
||||
var match = require('./matcher');
|
||||
var Promise = require('bluebird');
|
||||
var util = require('util');
|
||||
|
||||
function ClientList(lists) {
|
||||
var list = this;
|
||||
Emitter.call(this);
|
||||
list.clients = {};
|
||||
function add(client) {
|
||||
list.add(client);
|
||||
}
|
||||
var add = list.add.bind(list);
|
||||
if (lists instanceof Array) {
|
||||
lists.forEach(function (list) {
|
||||
list.each(add).on('add', add);
|
||||
@@ -17,9 +17,14 @@ function ClientList(lists) {
|
||||
}
|
||||
|
||||
var API = ClientList.prototype = new Emitter();
|
||||
API.setMaxListeners(Infinity);
|
||||
|
||||
API.constructor = ClientList;
|
||||
|
||||
API.chain = function (list) {
|
||||
return new this.constructor(list);
|
||||
};
|
||||
|
||||
API.each = function (cb) {
|
||||
var key;
|
||||
for (key in this.clients) {
|
||||
@@ -57,7 +62,7 @@ API.get = function (ID) {
|
||||
};
|
||||
|
||||
API.filter = function (query) {
|
||||
var list = new ClientList();
|
||||
var list = this.chain();
|
||||
function filter(client, ID) {
|
||||
if (query instanceof Function && query(client, ID)) {
|
||||
list.add(client);
|
||||
@@ -90,19 +95,12 @@ API.excluding = function (exclude) {
|
||||
return list;
|
||||
};
|
||||
|
||||
API.len = function () {
|
||||
if (Object.keys instanceof Function) {
|
||||
return Object.keys(this.clients).length;
|
||||
}
|
||||
var num = 0;
|
||||
this.each(function () {
|
||||
num += 1;
|
||||
});
|
||||
return num;
|
||||
};
|
||||
API.len = util.deprecate(function () {
|
||||
return this.length;
|
||||
}, 'Use `.length` instead of `.len()`');
|
||||
|
||||
API.run = function (cb, scope) {
|
||||
var key, done = 0, list = this, length = this.len();
|
||||
var key, done = 0, list = this, length = this.length;
|
||||
key = Math.random()
|
||||
.toString(36)
|
||||
.slice(2);
|
||||
@@ -131,7 +129,7 @@ API.run = function (cb, scope) {
|
||||
};
|
||||
|
||||
API.pluck = function (num) {
|
||||
var self, list = new ClientList();
|
||||
var self, list = this.chain();
|
||||
self = this;
|
||||
function measure(client) {
|
||||
if (!list.atCapacity) {
|
||||
@@ -139,7 +137,7 @@ API.pluck = function (num) {
|
||||
}
|
||||
}
|
||||
list.on('add', function () {
|
||||
if (list.len() === num) {
|
||||
if (list.length === num) {
|
||||
list.atCapacity = true;
|
||||
}
|
||||
});
|
||||
@@ -154,4 +152,17 @@ API.pluck = function (num) {
|
||||
|
||||
API.atCapacity = false;
|
||||
|
||||
Object.defineProperty(API, 'length', {
|
||||
get: function () {
|
||||
if (Object.keys instanceof Function) {
|
||||
return Object.keys(this.clients).length;
|
||||
}
|
||||
var num = 0;
|
||||
this.each(function () {
|
||||
num += 1;
|
||||
});
|
||||
return num;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ClientList;
|
||||
|
||||
17
src/index.js
17
src/index.js
@@ -1,16 +1,5 @@
|
||||
'use strict';
|
||||
var server = require('./server');
|
||||
var clients = require('./clients');
|
||||
var ClientList = require('./ClientList');
|
||||
|
||||
var msg = '\n\nAPI CHANGE: ".serve()" has been renamed to ".server()",\n' +
|
||||
'and no longer works the same (see changelog#v0.2.0).\n';
|
||||
|
||||
module.exports = {
|
||||
server: server,
|
||||
serve: function () {
|
||||
throw new Error(msg);
|
||||
},
|
||||
clients: clients,
|
||||
ClientList: ClientList
|
||||
};
|
||||
exports.server = require('./server');
|
||||
exports.clients = require('./clients');
|
||||
exports.ClientList = require('./ClientList');
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
/*eslint-disable no-sync*/
|
||||
'use strict';
|
||||
|
||||
var io = require('socket.io');
|
||||
var fs = require('fs');
|
||||
var clients = require('./clients');
|
||||
var file = require.resolve('panic-client/panic.js');
|
||||
var Server = require('http').Server;
|
||||
var panic = require('./index');
|
||||
var client;
|
||||
|
||||
Object.defineProperty(panic, 'client', {
|
||||
get: function () {
|
||||
if (!client) {
|
||||
client = fs.readFileSync(file, 'utf8');
|
||||
}
|
||||
return client;
|
||||
}
|
||||
});
|
||||
|
||||
function serve(req, res) {
|
||||
if (req.url === '/panic.js') {
|
||||
fs.createReadStream(file).pipe(res);
|
||||
res.end(panic.client);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,22 +35,22 @@ describe('A clientList', function () {
|
||||
|
||||
it('should return length when "len()" is called', function () {
|
||||
list.add(client);
|
||||
expect(list.len()).to.eq(1);
|
||||
expect(list.length).to.eq(1);
|
||||
list.remove(client);
|
||||
expect(list.len()).to.eq(0);
|
||||
expect(list.length).to.eq(0);
|
||||
});
|
||||
|
||||
it('should not add disconnected clients', function () {
|
||||
client.socket.connected = false;
|
||||
list.add(client);
|
||||
expect(list.len()).to.eq(0);
|
||||
expect(list.length).to.eq(0);
|
||||
});
|
||||
|
||||
it('should remove a client on disconnect', function () {
|
||||
list.add(client);
|
||||
expect(list.len()).to.eq(1);
|
||||
expect(list.length).to.eq(1);
|
||||
client.socket.emit('disconnect');
|
||||
expect(list.len()).to.eq(0);
|
||||
expect(list.length).to.eq(0);
|
||||
});
|
||||
|
||||
it('should resolve a promise when all clients finish', function (done) {
|
||||
@@ -88,11 +88,11 @@ describe('A clientList', function () {
|
||||
describe('filter', function () {
|
||||
it('should not mutate the original list', function () {
|
||||
list.add(client);
|
||||
expect(list.len()).to.eq(1);
|
||||
expect(list.length).to.eq(1);
|
||||
list.filter(function () {
|
||||
return false;
|
||||
});
|
||||
expect(list.len()).to.eq(1);
|
||||
expect(list.length).to.eq(1);
|
||||
});
|
||||
|
||||
it('should return a new, filtered list', function () {
|
||||
@@ -101,15 +101,15 @@ describe('A clientList', function () {
|
||||
var browsers = list.filter(function (client) {
|
||||
return client.platform.name !== 'Node.js';
|
||||
});
|
||||
expect(servers.len()).to.eq(1);
|
||||
expect(browsers.len()).to.eq(0);
|
||||
expect(servers.length).to.eq(1);
|
||||
expect(browsers.length).to.eq(0);
|
||||
});
|
||||
|
||||
it('should be reactive to changes to the parent list', function () {
|
||||
var servers = list.filter('Node.js');
|
||||
expect(servers.len()).to.eq(0);
|
||||
expect(servers.length).to.eq(0);
|
||||
list.add(client);
|
||||
expect(servers.len()).to.eq(1);
|
||||
expect(servers.length).to.eq(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -117,7 +117,7 @@ describe('A clientList', function () {
|
||||
it('should not contain excluded clients', function () {
|
||||
list.add(client);
|
||||
var filtered = list.excluding(list);
|
||||
expect(filtered.len()).to.eq(0);
|
||||
expect(filtered.length).to.eq(0);
|
||||
});
|
||||
|
||||
it('should react to removals if they are connected', function () {
|
||||
@@ -127,9 +127,9 @@ describe('A clientList', function () {
|
||||
.add(decoy);
|
||||
var filtered = list.excluding(exclusion);
|
||||
list.add(client).add(new Client());
|
||||
expect(filtered.len()).to.eq(1);
|
||||
expect(filtered.length).to.eq(1);
|
||||
exclusion.remove(client).remove(decoy);
|
||||
expect(filtered.len()).to.eq(2);
|
||||
expect(filtered.length).to.eq(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -138,26 +138,26 @@ describe('A clientList', function () {
|
||||
list.add(client)
|
||||
.add(new Client())
|
||||
.add(new Client());
|
||||
expect(list.pluck(1).len()).to.eq(1);
|
||||
expect(list.pluck(1).length).to.eq(1);
|
||||
});
|
||||
|
||||
it('should listen for additions', function () {
|
||||
var subset = list.pluck(2);
|
||||
expect(subset.len()).not.to.eq(2);
|
||||
expect(subset.length).not.to.eq(2);
|
||||
list.add(new Client()).add(new Client());
|
||||
expect(subset.len()).to.eq(2);
|
||||
expect(subset.length).to.eq(2);
|
||||
list.add(new Client());
|
||||
expect(subset.len()).to.eq(2);
|
||||
expect(subset.length).to.eq(2);
|
||||
});
|
||||
|
||||
it('should replace a client when it disconnects', function () {
|
||||
var subset = list.pluck(1);
|
||||
list.add(client).add(new Client());
|
||||
expect(subset.len()).to.eq(1);
|
||||
expect(subset.length).to.eq(1);
|
||||
client.socket.emit('disconnect');
|
||||
// It should be replaced with
|
||||
// the second connected client.
|
||||
expect(subset.len()).to.eq(1);
|
||||
expect(subset.length).to.eq(1);
|
||||
});
|
||||
|
||||
it('should set a flag whether the constraint is met', function () {
|
||||
@@ -175,8 +175,8 @@ describe('A clientList', function () {
|
||||
list.add(client)
|
||||
.add(new Client())
|
||||
.add(new Client());
|
||||
expect(alice.len()).to.eq(1);
|
||||
expect(bob.len()).to.eq(1);
|
||||
expect(alice.length).to.eq(1);
|
||||
expect(bob.length).to.eq(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -208,4 +208,19 @@ describe('The ClientList constructor', function () {
|
||||
list1.add(client3);
|
||||
expect(list.get(client3.socket.id)).to.eq(client3);
|
||||
});
|
||||
|
||||
it('should be subclassable', function () {
|
||||
function Sub() {
|
||||
ClientList.call(this);
|
||||
}
|
||||
Sub.prototype = new ClientList();
|
||||
Sub.prototype.constructor = Sub;
|
||||
|
||||
var sub = new Sub();
|
||||
expect(sub).to.be.an.instanceof(Sub);
|
||||
|
||||
// chained inheritance
|
||||
expect(sub.filter('Firefox')).to.be.an.instanceof(Sub);
|
||||
expect(sub.pluck(1)).to.be.an.instanceof(Sub);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user