Initial commit

This commit is contained in:
Nolan Lawson 2014-06-01 01:32:40 -07:00
commit e4aba1bd0b
15 changed files with 749 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
node_modules
.DS_Store
*~
coverage
test/test-bundle.js
npm-debug.log
dist

30
.jshintrc Executable file
View File

@ -0,0 +1,30 @@
{
"curly": true,
"eqeqeq": true,
"immed": true,
"newcap": true,
"noarg": true,
"sub": true,
"undef": true,
"unused": true,
"eqnull": true,
"browser": true,
"node": true,
"strict": true,
"globalstrict": true,
"globals": { "Pouch": true},
"white": true,
"indent": 2,
"maxlen": 100,
"predef": [
"process",
"global",
"require",
"console",
"describe",
"beforeEach",
"afterEach",
"it",
"emit"
]
}

30
.travis.yml Normal file
View File

@ -0,0 +1,30 @@
language: node_js
services:
- couchdb
node_js:
- "0.10"
script: npm run $COMMAND
before_script:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- "sleep 5"
# Workaround for Selenium #3280 issue
- "sudo sed -i 's/^127\\.0\\.0\\.1.*$/127.0.0.1 localhost/' /etc/hosts"
# Lets know what CouchDB we are playing with
# and make sure its logging debug
- "curl -X GET http://127.0.0.1:5984/"
- "curl -X PUT http://127.0.0.1:5984/_config/log/level -d '\"debug\"'"
after_failure:
- "curl -X GET http://127.0.0.1:5984/_log?bytes=1000000"
env:
matrix:
- COMMAND=test
- CLIENT=selenium:firefox COMMAND=test
- CLIENT=selenium:phantomjs COMMAND=test
- COMMAND=coverage

103
README.md Normal file
View File

@ -0,0 +1,103 @@
PouchDB Plugin Seed
=====
[![Build Status](https://travis-ci.org/pouchdb/plugin-seed.svg)](https://travis-ci.org/pouchdb/plugin-seed)
Fork this project to build your first PouchDB plugin. It contains everything you need to test in Node, WebSQL, and IndexedDB. It also includes a Travis config file so you
can automatically run the tests in Travis.
Building
----
npm install
npm run build
Your plugin is now located at `dist/pouchdb.mypluginname.js` and `dist/pouchdb.mypluginname.min.js` and is ready for distribution.
Getting Started
-------
**First**, change the `name` in `package.json` to whatever you want to call your plugin. Change the `build` script so that it writes to the desired filename (e.g. `pouchdb.mypluginname.js`). Also, change the authors, description, git repo, etc.
**Next**, modify the `index.js` to do whatever you want your plugin to do. Right now it just adds a `pouch.sayHello()` function that says hello:
```js
exports.sayHello = utils.toPromise(function (callback) {
callback(null, 'hello');
});
```
**Optionally**, you can add some tests in `tests/test.js`. These tests will be run both in the local database and a remote CouchDB, which is expected to be running at localhost:5984 in "Admin party" mode.
The sample test is:
```js
it('should say hello', function () {
return db.sayHello().then(function (response) {
response.should.equal('hello');
});
});
```
Testing
----
### In Node
This will run the tests in Node using LevelDB:
npm test
You can also check for 100% code coverage using:
npm run coverage
If you don't like the coverage results, change the values from 100 to something else in `package.json`, or add `/*istanbul ignore */` comments.
If you have mocha installed globally you can run single test with:
```
TEST_DB=local mocha --reporter spec --grep search_phrase
```
The `TEST_DB` environment variable specifies the database that PouchDB should use (see `package.json`).
### In the browser
Run `npm run dev` and then point your favorite browser to [http://127.0.0.1:8001/test/index.html](http://127.0.0.1:8001/test/index.html).
The query param `?grep=mysearch` will search for tests matching `mysearch`.
### Automated browser tests
You can run e.g.
CLIENT=selenium:firefox npm test
CLIENT=selenium:phantomjs npm test
This will run the tests automatically and the process will exit with a 0 or a 1 when it's done. Firefox uses IndexedDB, and PhantomJS uses WebSQL.
What to tell your users
--------
Below is some boilerplate you can use for when you want a real README for your users.
To use this plugin, include it after `pouchdb.js` in your HTML page:
```html
<script src="pouchdb.js"></script>
<script src="pouchdb.mypluginname.js"></script>
```
Or to use it in Node.js, just npm install it:
```
npm install pouchdb-myplugin
```
And then attach it to the `PouchDB` object:
```js
var PouchDB = require('pouchdb');
PouchDB.plugin(require('pouchdb-myplugin'));
```

94
bin/dev-server.js Executable file
View File

@ -0,0 +1,94 @@
#!/usr/bin/env node
'use strict';
var PouchDB = require('pouchdb');
var COUCH_HOST = process.env.COUCH_HOST || 'http://127.0.0.1:5984';
var HTTP_PORT = 8001;
var Promise = require('bluebird');
var request = require('request');
var http_server = require("http-server");
var fs = require('fs');
var indexfile = "./test/test.js";
var dotfile = "./test/.test-bundle.js";
var outfile = "./test/test-bundle.js";
var watchify = require("watchify");
var w = watchify(indexfile);
w.on('update', bundle);
bundle();
var filesWritten = false;
var serverStarted = false;
var readyCallback;
function bundle() {
var wb = w.bundle();
wb.on('error', function (err) {
console.error(String(err));
});
wb.on("end", end);
wb.pipe(fs.createWriteStream(dotfile));
function end() {
fs.rename(dotfile, outfile, function (err) {
if (err) { return console.error(err); }
console.log('Updated:', outfile);
filesWritten = true;
checkReady();
});
}
}
function startServers(callback) {
readyCallback = callback;
// enable CORS globally, because it's easier this way
var corsValues = {
'/_config/httpd/enable_cors': 'true',
'/_config/cors/origins': '*',
'/_config/cors/credentials': 'true',
'/_config/cors/methods': 'PROPFIND, PROPPATCH, COPY, MOVE, DELETE, ' +
'MKCOL, LOCK, UNLOCK, PUT, GETLIB, VERSION-CONTROL, CHECKIN, ' +
'CHECKOUT, UNCHECKOUT, REPORT, UPDATE, CANCELUPLOAD, HEAD, ' +
'OPTIONS, GET, POST',
'/_config/cors/headers':
'Cache-Control, Content-Type, Depth, Destination, ' +
'If-Modified-Since, Overwrite, User-Agent, X-File-Name, ' +
'X-File-Size, X-Requested-With, accept, accept-encoding, ' +
'accept-language, authorization, content-type, origin, referer'
};
Promise.all(Object.keys(corsValues).map(function (key) {
var value = corsValues[key];
return request({
method: 'put',
url: COUCH_HOST + key,
body: JSON.stringify(value)
});
})).then(function () {
return http_server.createServer().listen(HTTP_PORT);
}).then(function () {
console.log('Tests: http://127.0.0.1:' + HTTP_PORT + '/test/index.html');
serverStarted = true;
checkReady();
}).catch(function (err) {
if (err) {
console.log(err);
process.exit(1);
}
});
}
function checkReady() {
if (filesWritten && serverStarted && readyCallback) {
readyCallback();
}
}
if (require.main === module) {
startServers();
} else {
module.exports.start = startServers;
}

9
bin/run-test.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
: ${CLIENT:="node"}
if [ "$CLIENT" == "node" ]; then
npm run test-node
else
npm run test-browser
fi

176
bin/test-browser.js Executable file
View File

@ -0,0 +1,176 @@
#!/usr/bin/env node
'use strict';
var path = require('path');
var spawn = require('child_process').spawn;
var wd = require('wd');
var sauceConnectLauncher = require('sauce-connect-launcher');
var querystring = require("querystring");
var request = require('request').defaults({json: true});
var devserver = require('./dev-server.js');
var SELENIUM_PATH = '../vendor/selenium-server-standalone-2.38.0.jar';
var SELENIUM_HUB = 'http://localhost:4444/wd/hub/status';
var testTimeout = 30 * 60 * 1000;
var username = process.env.SAUCE_USERNAME;
var accessKey = process.env.SAUCE_ACCESS_KEY;
// process.env.CLIENT is a colon seperated list of
// (saucelabs|selenium):browserName:browserVerion:platform
var tmp = (process.env.CLIENT || 'selenium:firefox').split(':');
var client = {
runner: tmp[0] || 'selenium',
browser: tmp[1] || 'firefox',
version: tmp[2] || null, // Latest
platform: tmp[3] || null
};
var testUrl = 'http://127.0.0.1:8001/test/index.html';
var qs = {};
var sauceClient;
var sauceConnectProcess;
var tunnelId = process.env.TRAVIS_JOB_NUMBER || 'tunnel-' + Date.now();
if (client.runner === 'saucelabs') {
qs.saucelabs = true;
}
if (process.env.GREP) {
qs.grep = process.env.GREP;
}
if (process.env.ADAPTERS) {
qs.adapters = process.env.ADAPTERS;
}
if (process.env.ES5_SHIM || process.env.ES5_SHIMS) {
qs.es5shim = true;
}
testUrl += '?';
testUrl += querystring.stringify(qs);
if (process.env.TRAVIS &&
client.browser !== 'firefox' &&
process.env.TRAVIS_SECURE_ENV_VARS === 'false') {
console.error('Not running test, cannot connect to saucelabs');
process.exit(0);
return;
}
function testError(e) {
console.error(e);
console.error('Doh, tests failed');
sauceClient.quit();
process.exit(3);
}
function postResult(result) {
process.exit(!process.env.PERF && result.failed ? 1 : 0);
}
function testComplete(result) {
console.log(result);
sauceClient.quit().then(function () {
if (sauceConnectProcess) {
sauceConnectProcess.close(function () {
postResult(result);
});
} else {
postResult(result);
}
});
}
function startSelenium(callback) {
// Start selenium
spawn('java', ['-jar', path.resolve(__dirname, SELENIUM_PATH)], {});
var retries = 0;
var started = function () {
if (++retries > 30) {
console.error('Unable to connect to selenium');
process.exit(1);
return;
}
request(SELENIUM_HUB, function (err, resp) {
if (resp && resp.statusCode === 200) {
sauceClient = wd.promiseChainRemote();
callback();
} else {
setTimeout(started, 1000);
}
});
};
started();
}
function startSauceConnect(callback) {
var options = {
username: username,
accessKey: accessKey,
tunnelIdentifier: tunnelId
};
sauceConnectLauncher(options, function (err, process) {
if (err) {
console.error('Failed to connect to saucelabs');
console.error(err);
return process.exit(1);
}
sauceConnectProcess = process;
sauceClient = wd.promiseChainRemote("localhost", 4445, username, accessKey);
callback();
});
}
function startTest() {
console.log('Starting', client);
var opts = {
browserName: client.browser,
version: client.version,
platform: client.platform,
tunnelTimeout: testTimeout,
name: client.browser + ' - ' + tunnelId,
'max-duration': 60 * 30,
'command-timeout': 599,
'idle-timeout': 599,
'tunnel-identifier': tunnelId
};
sauceClient.init(opts).get(testUrl, function () {
/* jshint evil: true */
var interval = setInterval(function () {
sauceClient.eval('window.results', function (err, results) {
if (err) {
clearInterval(interval);
testError(err);
} else if (results.completed || results.failures.length) {
clearInterval(interval);
testComplete(results);
} else {
console.log('=> ', results);
}
});
}, 10 * 1000);
});
}
devserver.start(function () {
if (client.runner === 'saucelabs') {
startSauceConnect(startTest);
} else {
startSelenium(startTest);
}
});

19
index.js Normal file
View File

@ -0,0 +1,19 @@
'use strict';
var utils = require('./pouch-utils');
exports.sayHello = utils.toPromise(function (callback) {
//
// You can use the following code to
// get the pouch or PouchDB objects
//
// var pouch = this;
// var PouchDB = pouch.constructor;
callback(null, 'hello');
});
/* istanbul ignore next */
if (typeof window !== 'undefined' && window.PouchDB) {
window.PouchDB.plugin(exports);
}

68
package.json Normal file
View File

@ -0,0 +1,68 @@
{
"name": "pouchdb-plugin-seed",
"version": "0.1.0",
"description": "PouchDB Plugin Seed project",
"main": "index.js",
"repository": {
"type": "git",
"url": "git://github.com/pouchdb/plugin-seed.git"
},
"keywords": [
"pouch",
"pouchdb",
"plugin",
"seed",
"couch",
"couchdb"
],
"author": "",
"license": "Apache",
"bugs": {
"url": "https://github.com/pouchdb/plugin-seed/issues"
},
"scripts": {
"test-node": "TEST_DB=testdb,http://localhost:5984/testdb istanbul test ./node_modules/mocha/bin/_mocha test/test.js",
"test-browser": "./bin/test-browser.js",
"jshint": "jshint -c .jshintrc *.js test/test.js",
"test": "npm run jshint && ./bin/run-test.sh",
"build": "mkdir -p dist && browserify index.js -o dist/pouchdb.mypluginname.js && npm run min",
"min": "uglifyjs dist/pouchdb.mypluginname.js -mc > dist/pouchdb.mypluginname.min.js",
"dev": "browserify test/test.js > test/test-bundle.js && npm run dev-server",
"dev-server": "./bin/dev-server.js",
"coverage": "npm test --coverage && istanbul check-coverage --lines 100 --function 100 --statements 100 --branches 100"
},
"dependencies": {
"lie": "^2.6.0",
"inherits": "~2.0.1",
"argsarray": "0.0.1",
"es3ify": "^0.1.3"
},
"devDependencies": {
"bluebird": "^1.0.7",
"browserify": "~2.36.0",
"chai": "~1.8.1",
"chai-as-promised": "~4.1.0",
"http-server": "~0.5.5",
"istanbul": "^0.2.7",
"jshint": "~2.3.0",
"mocha": "~1.18",
"phantomjs": "^1.9.7-5",
"pouchdb": "pouchdb/pouchdb",
"request": "^2.36.0",
"sauce-connect-launcher": "^0.4.2",
"uglifyjs": "^2.3.6",
"watchify": "~0.4.1",
"wd": "^0.2.21"
},
"peerDependencies": {
"pouchdb": ">= 2.2.0"
},
"browser": {
"crypto": false
},
"browserify": {
"transform": [
"es3ify"
]
}
}

82
pouch-utils.js Normal file
View File

@ -0,0 +1,82 @@
'use strict';
var Promise;
/* istanbul ignore next */
if (typeof window !== 'undefined' && window.PouchDB) {
Promise = window.PouchDB.utils.Promise;
} else {
Promise = typeof global.Promise === 'function' ? global.Promise : require('lie');
}
/* istanbul ignore next */
exports.once = function (fun) {
var called = false;
return exports.getArguments(function (args) {
if (called) {
console.trace();
throw new Error('once called more than once');
} else {
called = true;
fun.apply(this, args);
}
});
};
/* istanbul ignore next */
exports.getArguments = function (fun) {
return function () {
var len = arguments.length;
var args = new Array(len);
var i = -1;
while (++i < len) {
args[i] = arguments[i];
}
return fun.call(this, args);
};
};
/* istanbul ignore next */
exports.toPromise = function (func) {
//create the function we will be returning
return exports.getArguments(function (args) {
var self = this;
var tempCB = (typeof args[args.length - 1] === 'function') ? args.pop() : false;
// if the last argument is a function, assume its a callback
var usedCB;
if (tempCB) {
// if it was a callback, create a new callback which calls it,
// but do so async so we don't trap any errors
usedCB = function (err, resp) {
process.nextTick(function () {
tempCB(err, resp);
});
};
}
var promise = new Promise(function (fulfill, reject) {
try {
var callback = exports.once(function (err, mesg) {
if (err) {
reject(err);
} else {
fulfill(mesg);
}
});
// create a callback for this invocation
// apply the function in the orig context
args.push(callback);
func.apply(self, args);
} catch (e) {
reject(e);
}
});
// if there is a callback, call it back
if (usedCB) {
promise.then(function (result) {
usedCB(null, result);
}, usedCB);
}
promise.cancel = function () {
return this;
};
return promise;
});
};
exports.inherits = require('inherits');

28
test/bind-polyfill.js Normal file
View File

@ -0,0 +1,28 @@
(function () {
'use strict';
// minimal polyfill for phantomjs; in the future, we should do ES5_SHIM=true like pouchdb
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
})();

18
test/index.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Mocha Tests</title>
<link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
<script src="./bind-polyfill.js"></script>
<script src="../node_modules/mocha/mocha.js"></script>
<script>
mocha.setup('bdd');
</script>
<script src="./test-bundle.js"></script>
</head>
<body>
<div id="mocha"></div>
<script type="text/javascript" src="./webrunner.js"></script>
</body>
</html>

52
test/test.js Normal file
View File

@ -0,0 +1,52 @@
/*jshint expr:true */
'use strict';
var Pouch = require('pouchdb');
//
// your plugin goes here
//
var helloPlugin = require('../');
Pouch.plugin(helloPlugin);
var chai = require('chai');
chai.use(require("chai-as-promised"));
//
// more variables you might want
//
chai.should(); // var should = chai.should();
require('bluebird'); // var Promise = require('bluebird');
var dbs;
if (process.browser) {
dbs = 'testdb' + Math.random() +
',http://localhost:5984/testdb' + Math.round(Math.random() * 100000);
} else {
dbs = process.env.TEST_DB;
}
dbs.split(',').forEach(function (db) {
var dbType = /^http/.test(db) ? 'http' : 'local';
tests(db, dbType);
});
function tests(dbName, dbType) {
var db;
beforeEach(function () {
db = new Pouch(dbName);
return db;
});
afterEach(function () {
return Pouch.destroy(dbName);
});
describe(dbType + ': hello test suite', function () {
it('should say hello', function () {
return db.sayHello().then(function (response) {
response.should.equal('hello');
});
});
});
}

33
test/webrunner.js Normal file
View File

@ -0,0 +1,33 @@
/* global mocha: true */
(function () {
'use strict';
var runner = mocha.run();
window.results = {
lastPassed: '',
passed: 0,
failed: 0,
failures: []
};
runner.on('pass', function (e) {
window.results.lastPassed = e.title;
window.results.passed++;
});
runner.on('fail', function (e) {
window.results.failed++;
window.results.failures.push({
title: e.title,
message: e.err.message,
stack: e.err.stack
});
});
runner.on('end', function () {
window.results.completed = true;
window.results.passed++;
});
})();

Binary file not shown.