GT2/GT2-Android/node_modules/websql/test/test.changes.js

2511 lines
73 KiB
JavaScript

'use strict';
var PouchDB = require('./pouchdb');
var should = require('chai').should();
var testUtils = require('./test.utils.js');
var adapters = ['local'];
adapters.forEach(function (adapter) {
describe('test.changes.js-' + adapter, function () {
var dbs = {};
// if it exists, return the single element
// which has the specific id. Else retun null.
// useful for finding elements within a _changes feed
function findById(array, id) {
var result = array.filter(function (i) {
return i.id === id;
});
//
if (result.length === 1) {
return result[0];
}
}
beforeEach(function (done) {
dbs.name = testUtils.adapterUrl(adapter, 'testdb');
dbs.remote = testUtils.adapterUrl(adapter, 'test_repl_remote');
testUtils.cleanup([dbs.name, dbs.remote], done);
});
after(function (done) {
testUtils.cleanup([dbs.name, dbs.remote], done);
});
it('All changes', function (done) {
var db = new PouchDB(dbs.name);
db.post({ test: 'somestuff' }, function () {
var promise = db.changes({
}).on('change', function (change) {
change.should.not.have.property('doc');
change.should.have.property('seq');
done();
});
should.exist(promise);
promise.cancel.should.be.a('function');
});
});
it('Promise resolved when changes cancelled', function (done) {
var docs = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2},
{_id: '3', integer: 3},
{_id: '4', integer: 4},
{_id: '5', integer: 5},
{_id: '6', integer: 6},
{_id: '7', integer: 7},
{_id: '8', integer: 9},
{_id: '9', integer: 9},
{_id: '10', integer: 10}
];
var db = new PouchDB(dbs.name);
db.bulkDocs({ docs: docs }, function () {
var changeCount = 0;
var promise = db.changes().on('change', function handler() {
changeCount++;
if (changeCount === 5) {
promise.cancel();
promise.removeListener('change', handler);
}
});
should.exist(promise);
should.exist(promise.then);
promise.then.should.be.a('function');
promise.then(
function (result) {
changeCount.should.equal(5, 'changeCount');
should.exist(result);
result.should.deep.equal({status: 'cancelled'});
done();
}, function (err) {
changeCount.should.equal(5, 'changeCount');
should.exist(err);
done();
});
});
});
it('Changes Since', function (done) {
var docs1 = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2},
{_id: '3', integer: 3},
{_id: '4', integer: 4},
{_id: '5', integer: 5},
{_id: '6', integer: 6},
{_id: '7', integer: 7},
{_id: '8', integer: 9},
{_id: '9', integer: 9},
{_id: '10', integer: 10},
{_id: '11', integer: 11}
];
var db = new PouchDB(dbs.name);
db.bulkDocs({ docs: docs1 }, function () {
db.info(function (err, info) {
var update_seq = info.update_seq;
var docs2 = [
{_id: '12', integer: 12},
{_id: '13', integer: 13}
];
db.bulkDocs({ docs: docs2 }, function () {
var promise = db.changes({
since: update_seq
}).on('complete', function (results) {
results.results.length.should.be.at.least(2);
done();
});
should.exist(promise);
promise.cancel.should.be.a('function');
});
});
});
});
it('Changes Since and limit limit 1', function (done) {
var docs1 = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2}
];
var db = new PouchDB(dbs.name);
db.bulkDocs({ docs: docs1 }, function () {
db.info(function (err, info) {
var update_seq = info.update_seq;
var docs2 = [
{_id: '3', integer: 3},
{_id: '4', integer: 4}
];
db.bulkDocs({ docs: docs2 }, function () {
db.changes({
since: update_seq,
limit: 1
}).on('complete', function (results) {
results.results.length.should.equal(1);
done();
});
});
});
});
});
it('Changes Since and limit limit 0', function (done) {
var docs1 = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2}
];
var db = new PouchDB(dbs.name);
db.bulkDocs({ docs: docs1 }, function () {
db.info(function (err, info) {
var update_seq = info.update_seq;
var docs2 = [
{_id: '3', integer: 3},
{_id: '4', integer: 4}
];
db.bulkDocs({ docs: docs2 }, function () {
db.changes({
since: update_seq,
limit: 0
}).on('complete', function (results) {
results.results.length.should.equal(1);
done();
});
});
});
});
});
it('Changes limit', function (done) {
var docs1 = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2},
{_id: '3', integer: 3}
];
var docs2 = [
{_id: '2', integer: 11},
{_id: '3', integer: 12}
];
var db = new PouchDB(dbs.name);
// we use writeDocs since bulkDocs looks to have undefined
// order of doing insertions
testUtils.writeDocs(db, docs1, function (err, info) {
docs2[0]._rev = info[2].rev;
docs2[1]._rev = info[3].rev;
db.info(function (err, info) {
var update_seq = info.update_seq;
db.put(docs2[0], function (err, info) {
docs2[0]._rev = info.rev;
db.put(docs2[1], function (err, info) {
docs2[1]._rev = info.rev;
db.changes({
limit: 2,
since: update_seq,
include_docs: true
}).on('complete', function (results) {
results = results.results;
results.length.should.equal(2);
// order is not guaranteed
var first = results[0];
var second = results[1];
if (first.id === '3') {
second = first;
first = results[1];
}
first.id.should.equal('2');
first.doc.integer.should.equal(docs2[0].integer);
first.doc._rev.should.equal(docs2[0]._rev);
second.id.should.equal('3');
second.doc.integer.should.equal(docs2[1].integer);
second.doc._rev.should.equal(docs2[1]._rev);
done();
});
});
});
});
});
});
it('Changes with filter not present in ddoc', function (done) {
var docs = [
{_id: '1', integer: 1},
{ _id: '_design/foo',
integer: 4,
filters: { even: 'function (doc) { return doc.integer % 2 === 1; }' }
}
];
var db = new PouchDB(dbs.name);
testUtils.writeDocs(db, docs, function () {
db.changes({
filter: 'foo/odd',
limit: 2,
include_docs: true
}).on('error', function (err) {
err.status.should.equal(PouchDB.Errors.MISSING_DOC.status,
'correct error status returned');
err.message.should.equal(PouchDB.Errors.MISSING_DOC.message,
'correct error message returned');
// todo: does not work in pouchdb-server.
// err.reason.should.equal('missing json key: odd');
done();
});
});
});
it('Changes with `filters` key not present in ddoc', function (done) {
var docs = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{
_id: '_design/foo',
integer: 4,
views: {
even: {
map: 'function (doc) { if (doc.integer % 2 === 1)' +
' { emit(doc._id, null) }; }'
}
}
}
];
var db = new PouchDB(dbs.name);
testUtils.writeDocs(db, docs, function () {
db.changes({
filter: 'foo/even',
limit: 2,
include_docs: true
}).on('error', function (err) {
err.status.should.equal(PouchDB.Errors.MISSING_DOC.status,
'correct error status returned');
err.message.should.equal(PouchDB.Errors.MISSING_DOC.message,
'correct error message returned');
// todo: does not work in pouchdb-server.
// err.reason.should.equal('missing json key: filters');
done();
});
});
});
it('Changes limit and filter', function (done) {
var docs1 = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2}
];
var db = new PouchDB(dbs.name);
var docs2 = [
{_id: '3', integer: 3},
{_id: '4', integer: 4},
{_id: '5', integer: 5},
{
_id: '_design/foo',
integer: 4,
filters: { even: 'function (doc) { return doc.integer % 2 === 1; }' }
}
];
db.bulkDocs({ docs: docs1 }, function () {
db.info(function (err, info) {
var update_seq = info.update_seq;
testUtils.writeDocs(db, docs2, function () {
var promise = db.changes({
filter: 'foo/even',
limit: 2,
since: update_seq,
include_docs: true
}).on('complete', function (results) {
results.results.length.should.equal(2);
var three = findById(results.results, '3');
three.doc.integer.should.equal(3);
var five = findById(results.results, '5');
five.doc.integer.should.equal(5);
done();
}).on('error', done);
should.exist(promise);
promise.cancel.should.be.a('function');
});
});
});
});
it('Changes with shorthand function name', function (done) {
var docs = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2},
{
_id: '_design/even',
integer: 3,
filters: { even: 'function (doc) { return doc.integer % 2 === 0; }' }
}
];
var db = new PouchDB(dbs.name);
db.bulkDocs({ docs: docs }, function () {
var promise = db.changes({
filter: 'even',
include_docs: true
}).on('complete', function (results) {
results.results.length.should.equal(2);
var zero = findById(results.results, '0');
zero.doc.integer.should.equal(0);
var two = findById(results.results, '2');
two.doc.integer.should.equal(2);
done();
}).on('error', done);
should.exist(promise);
promise.cancel.should.be.a('function');
});
});
it('Changes with filter from nonexistent ddoc', function (done) {
var docs = [
{_id: '0', integer: 0},
{_id: '1', integer: 1}
];
var db = new PouchDB(dbs.name);
testUtils.writeDocs(db, docs, function () {
db.changes({
filter: 'foobar/odd'
}).on('error', function (err) {
should.exist(err);
done();
});
});
});
it('Changes with view not present in ddoc', function (done) {
var docs = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{
_id: '_design/foo',
integer: 4,
views:
{ even:
{ map: 'function (doc) { if (doc.integer % 2 === 1) { ' +
'emit(doc._id, null) }; }' } }
}
];
var db = new PouchDB(dbs.name);
testUtils.writeDocs(db, docs, function () {
db.changes({
filter: '_view',
view: 'foo/odd'
}).on('error', function (err) {
err.status.should.equal(PouchDB.Errors.MISSING_DOC.status,
'correct error status returned');
err.message.should.equal(PouchDB.Errors.MISSING_DOC.message,
'correct error message returned');
// todo: does not work in pouchdb-server.
// err.reason.should.equal('missing json key: odd');
done();
});
});
});
it('Changes with `views` key not present in ddoc', function (done) {
var docs = [
{_id: '1', integer: 1},
{
_id: '_design/foo',
integer: 4,
filters: { even: 'function (doc) { return doc.integer % 2 === 1; }' }
}
];
var db = new PouchDB(dbs.name);
testUtils.writeDocs(db, docs, function () {
db.changes({
filter: '_view',
view: 'foo/even'
}).on('error', function (err) {
err.status.should.equal(PouchDB.Errors.MISSING_DOC.status,
'correct error status returned');
err.message.should.equal(PouchDB.Errors.MISSING_DOC.message,
'correct error message returned');
// todo: does not work in pouchdb-server.
// err.reason.should.equal('missing json key: views',
// 'correct error reason returned');
done();
});
});
});
it('#4451 Changes with invalid view filter', function (done) {
var docs = [
{_id: '1', integer: 1},
{
_id: '_design/foo',
filters: { even: 'function (doc) { return doc.integer % 2 === 1; }' }
}
];
var db = new PouchDB(dbs.name);
db.bulkDocs(docs).then(function() {
db.changes({filter: 'a/b/c'}).on('error', function () {
done('should not be called');
}).on('complete', function() {
done();
});
});
});
it('3356 throw inside a filter', function (done) {
var db = new PouchDB(dbs.name);
db.put({
_id: "_design/test",
filters: {
test: function () {
throw new Error(); // syntaxerrors can't be caught either.
}.toString()
}
}).then(function() {
db.changes({filter: 'test/test'}).then(function() {
done('should have thrown');
}).catch(function () {
done();
});
});
});
it('Changes with missing param `view` in request', function (done) {
var docs = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{
_id: '_design/foo',
integer: 4,
views: { even: { map: 'function (doc) { if (doc.integer % 2 === 1) ' +
'{ emit(doc._id, null) }; }' } }
}
];
var db = new PouchDB(dbs.name);
testUtils.writeDocs(db, docs, function () {
db.changes({
filter: '_view'
}).on('error', function (err) {
err.status.should.equal(PouchDB.Errors.BAD_REQUEST.status,
'correct error status returned');
err.message.should.equal(PouchDB.Errors.BAD_REQUEST.message,
'correct error message returned');
// todo: does not work in pouchdb-server.
// err.reason.should
// .equal('`view` filter parameter is not provided.',
// 'correct error reason returned');
done();
});
});
});
it('Changes limit and view instead of filter', function (done) {
var docs1 = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2}
];
var db = new PouchDB(dbs.name);
db.bulkDocs({ docs: docs1 }, function () {
db.info(function (err, info) {
var update_seq = info.update_seq;
var docs2 = [
{_id: '3', integer: 3},
{_id: '4', integer: 4},
{_id: '5', integer: 5},
{
_id: '_design/foo',
integer: 4,
views: { even: { map: 'function (doc) ' +
'{ if (doc.integer % 2 === 1) ' +
'{ emit(doc._id, null) }; }'
}
}
}
];
db.bulkDocs({ docs: docs2 }, function () {
db.changes({
filter: '_view',
view: 'foo/even',
limit: 2,
since: update_seq,
include_docs: true
}).on('complete', function (results) {
var changes = results.results;
changes.length.should.equal(2);
findById(changes, '3')
.doc.integer.should.equal(3);
findById(changes, '5')
.doc.integer.should.equal(5);
done();
}).on('error', done);
});
});
});
});
it('Changes last_seq', function (done) {
// this test doesn't really make sense for clustered
// CouchDB because changes is unordered and last_seq might
// not equal the last seq in the _changes feed (although it
// should evaluate to the same thing on the server).
if (testUtils.isCouchMaster()) {
return done();
}
var docs = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2},
{_id: '3', integer: 3},
{
_id: '_design/foo',
integer: 4,
filters: { even: 'function (doc) { return doc.integer % 2 === 1; }' }
}
];
var db = new PouchDB(dbs.name);
db.changes().on('complete', function (results) {
results.last_seq.should.equal(0);
db.bulkDocs({ docs: docs }, function () {
db.changes().on('complete', function (results) {
results.last_seq.should.equal(5);
db.changes({
filter: 'foo/even'
}).on('complete', function (results) {
results.last_seq.should.equal(5);
results.results.length.should.equal(2);
done();
}).on('error', done);
}).on('error', done);
});
}).on('error', done);
});
it('Immediately cancel changes', function () {
// fixes code coverage by ensuring the changes() listener
// emits 'complete' even if the db's task queue isn't
// ready yet
return new PouchDB.utils.Promise(function (resolve, reject) {
var db = new PouchDB(dbs.name);
var changes = db.changes({live: true});
changes.on('error', reject);
changes.on('complete', resolve);
changes.cancel();
});
});
it('Changes with invalid ddoc view name', function () {
return new PouchDB.utils.Promise(function (resolve, reject) {
var db = new PouchDB(dbs.name);
db.post({});
var changes = db.changes({live: true, filter: '_view', view: ''});
changes.on('error', resolve);
changes.on('change', reject);
});
});
it('Changes with invalid ddoc view name 2', function () {
return new PouchDB.utils.Promise(function (resolve, reject) {
var db = new PouchDB(dbs.name);
db.post({});
var changes = db.changes({live: true, filter: '_view', view: 'a/b/c'});
changes.on('error', resolve);
changes.on('change', reject);
});
});
if (adapter === 'local') {
// This test crashes due to an invalid JSON response from CouchDB:
// https://issues.apache.org/jira/browse/COUCHDB-2765
// We could remove the "if" check and put a try/catch in our
// JSON parsing, but since this is a super-rare bug it may not be
// worth our time. This test does increase code coverage for our
// own local code, though.
it('Changes with invalid ddoc with no map function', function () {
// CouchDB 2.X does not allow saving of invalid design docs,
// so this test is not valid
if (testUtils.isCouchMaster()) {
return PouchDB.utils.Promise.resolve();
}
var db = new PouchDB(dbs.name);
return db.put({
_id: '_design/name',
views: {
name: {
empty: 'sad face'
}
}
}).then(function () {
return new PouchDB.utils.Promise(function (resolve, reject) {
var changes = db.changes({
live: true,
filter: '_view',
view: 'name/name'
});
changes.on('error', resolve);
changes.on('change', reject);
});
});
});
}
it('Changes with invalid ddoc with no filter function', function () {
// CouchDB 2.X does not allow saving of invalid design docs,
// so this test is not valid
if (testUtils.isCouchMaster()) {
return PouchDB.utils.Promise.resolve();
}
var db = new PouchDB(dbs.name);
return db.put({
_id: '_design/name',
views: {
name: {
empty: 'sad face'
}
}
}).then(function () {
return new PouchDB.utils.Promise(function (resolve, reject) {
var changes = db.changes({
live: true,
filter: 'name/name'
});
changes.on('error', resolve);
changes.on('change', reject);
});
});
});
it('Changes last_seq with view instead of filter', function (done) {
// this test doesn't really make sense for clustered
// CouchDB because changes is unordered and last_seq might
// not equal the last seq in the _changes feed (although it
// should evaluate to the same thing on the server).
if (testUtils.isCouchMaster()) {
return done();
}
var docs = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2},
{_id: '3', integer: 3},
{
_id: '_design/foo',
integer: 4,
views:
{ even:
{ map: 'function (doc) { if (doc.integer % 2 === 1) { ' +
'emit(doc._id, null) }; }' } }
}
];
var db = new PouchDB(dbs.name);
db.changes().on('complete', function (results) {
results.last_seq.should.equal(0);
db.bulkDocs({ docs: docs }, function () {
db.changes().on('complete', function (results) {
results.last_seq.should.equal(5);
db.changes({
filter: '_view',
view: 'foo/even'
}).on('complete', function (results) {
results.last_seq.should.equal(5);
results.results.length.should.equal(2);
done();
}).on('error', done);
}).on('error', done);
});
}).on('error', done);
});
it('Changes with style = all_docs', function (done) {
var simpleTree = [
[{_id: 'foo', _rev: '1-a', value: 'foo a'},
{_id: 'foo', _rev: '2-b', value: 'foo b'},
{_id: 'foo', _rev: '3-c', value: 'foo c'}],
[{_id: 'foo', _rev: '1-a', value: 'foo a'},
{_id: 'foo', _rev: '2-d', value: 'foo d'},
{_id: 'foo', _rev: '3-e', value: 'foo e'},
{_id: 'foo', _rev: '4-f', value: 'foo f'}],
[{_id: 'foo', _rev: '1-a', value: 'foo a'},
{_id: 'foo', _rev: '2-g', value: 'foo g', _deleted: true}]
];
var db = new PouchDB(dbs.name);
testUtils.putTree(db, simpleTree, function () {
db.changes().on('complete', function (res) {
res.results[0].changes.length.should.equal(1);
res.results[0].changes[0].rev.should.equal('4-f');
db.changes({
style: 'all_docs'
}).on('complete', function (res) {
res.results[0].changes.length.should.equal(3);
var changes = res.results[0].changes;
changes.sort(function (a, b) {
return a.rev < b.rev;
});
changes[0].rev.should.equal('4-f');
changes[1].rev.should.equal('3-c');
changes[2].rev.should.equal('2-g');
done();
}).on('error', done);
}).on('error', done);
});
});
it('Changes with style = all_docs and a callback for complete',
function (done) {
var simpleTree = [
[{_id: 'foo', _rev: '1-a', value: 'foo a'},
{_id: 'foo', _rev: '2-b', value: 'foo b'},
{_id: 'foo', _rev: '3-c', value: 'foo c'}],
[{_id: 'foo', _rev: '1-a', value: 'foo a'},
{_id: 'foo', _rev: '2-d', value: 'foo d'},
{_id: 'foo', _rev: '3-e', value: 'foo e'},
{_id: 'foo', _rev: '4-f', value: 'foo f'}],
[{_id: 'foo', _rev: '1-a', value: 'foo a'},
{_id: 'foo', _rev: '2-g', value: 'foo g', _deleted: true}]
];
var db = new PouchDB(dbs.name);
testUtils.putTree(db, simpleTree, function () {
db.changes(function (err, res) {
res.results[0].changes.length.should.equal(1);
res.results[0].changes[0].rev.should.equal('4-f');
db.changes({
style: 'all_docs'
}, function (err, res) {
should.not.exist(err);
res.results[0].changes.length.should.equal(3);
var changes = res.results[0].changes;
changes.sort(function (a, b) {
return a.rev < b.rev;
});
changes[0].rev.should.equal('4-f');
changes[1].rev.should.equal('3-c');
changes[2].rev.should.equal('2-g');
done();
});
});
});
});
it('Changes limit = 0', function (done) {
var docs = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2},
{_id: '3', integer: 3}
];
var db = new PouchDB(dbs.name);
db.bulkDocs({ docs: docs }, function () {
db.changes({
limit: 0
}).on('complete', function (results) {
results.results.length.should.equal(1);
done();
}).on('error', done);
});
});
// Note for the following test that CouchDB's implementation of /_changes
// with `descending=true` ignores any `since` parameter.
it('Descending changes', function (done) {
// _changes in CouchDB 2.0 does not guarantee order
// so skip this test
if (testUtils.isCouchMaster()) {
return done();
}
var db = new PouchDB(dbs.name);
db.post({_id: '0', test: 'ing'}, function () {
db.post({_id: '1', test: 'ing'}, function () {
db.post({_id: '2', test: 'ing'}, function () {
db.changes({
descending: true,
since: 1
}).on('complete', function (results) {
results.results.length.should.equal(3);
var ids = ['2', '1', '0'];
results.results.forEach(function (row, i) {
row.id.should.equal(ids[i]);
});
done();
}).on('error', done);
});
});
});
});
it('Changes doc', function (done) {
var db = new PouchDB(dbs.name);
db.post({ test: 'somestuff' }, function () {
db.changes({
include_docs: true
}).on('change', function (change) {
change.doc._id.should.equal(change.id);
change.doc._rev.should
.equal(change.changes[change.changes.length - 1].rev);
done();
});
});
});
// Note for the following test that CouchDB's implementation of /_changes
// with `descending=true` ignores any `since` parameter.
it('Descending many changes', function (done) {
var db = new PouchDB(dbs.name);
var docs = [];
var num = 100;
for (var i = 0; i < num; i++) {
docs.push({
_id: 'doc_' + i,
foo: 'bar_' + i
});
}
var changes = 0;
db.bulkDocs({ docs: docs }, function (err) {
if (err) {
return done(err);
}
db.changes({
descending: true
}).on('change', function () {
changes++;
}).on('complete', function () {
changes.should.equal(num, 'correct number of changes');
done();
}).on('error', function (err) {
done(err);
});
});
});
it('live-changes', function (done) {
var db = new PouchDB(dbs.name);
var count = 0;
var changes = db.changes({
live: true
}).on('complete', function () {
count.should.equal(1);
done();
}).on('change', function (change) {
count += 1;
change.should.not.have.property('doc');
count.should.equal(1);
changes.cancel();
});
db.post({ test: 'adoc' });
});
it('Multiple watchers', function (done) {
var db = new PouchDB(dbs.name);
var count = 0;
var changes1Complete = false;
var changes2Complete = false;
function checkCount() {
if (changes1Complete && changes2Complete) {
count.should.equal(2);
done();
}
}
var changes1 = db.changes({
live: true
}).on('complete', function () {
changes1Complete = true;
checkCount();
}).on('change', function () {
count += 1;
changes1.cancel();
changes1 = null;
}).on('error', done);
var changes2 = db.changes({
live: true
}).on('complete', function () {
changes2Complete = true;
checkCount();
}).on('change', function () {
count += 1;
changes2.cancel();
changes2 = null;
}).on('error', done);
db.post({test: 'adoc'});
});
it('Continuous changes doc', function (done) {
var db = new PouchDB(dbs.name);
var changes = db.changes({
live: true,
include_docs: true
}).on('complete', function (result) {
result.status.should.equal('cancelled');
done();
}).on('change', function (change) {
change.should.have.property('doc');
change.doc.should.have.property('_rev');
changes.cancel();
}).on('error', done);
db.post({ test: 'adoc' });
});
it('Cancel changes', function (done) {
var db = new PouchDB(dbs.name);
var count = 0;
var interval;
var docPosted = false;
// We want to wait for a period of time after the final
// document was posted to ensure we didnt see another
// change
function waitForDocPosted() {
if (!docPosted) {
return;
}
clearInterval(interval);
setTimeout(function () {
count.should.equal(1);
done();
}, 200);
}
var changes = db.changes({
live: true
}).on('complete', function (result) {
result.status.should.equal('cancelled');
// This setTimeout ensures that once we cancel a change we dont recieve
// subsequent callbacks, so it is needed
interval = setInterval(waitForDocPosted, 100);
}).on('change', function () {
count += 1;
if (count === 1) {
changes.cancel();
db.post({ test: 'another doc' }, function (err) {
if (err) {
return done(err);
}
docPosted = true;
});
}
});
db.post({ test: 'adoc' });
});
it("#3579 changes firing 1 too many times", function () {
var db = new PouchDB(dbs.name);
var Promise = PouchDB.utils.Promise;
return db.bulkDocs([{}, {}, {}]).then(function () {
var changes = db.changes({
since: 'now',
live: true,
include_docs: true
});
return Promise.all([
new Promise(function (resolve, reject) {
changes.on('error', reject);
changes.on('change', function (change) {
changes.cancel();
resolve(change);
});
}),
new Promise(function (resolve) {
setTimeout(resolve, 50);
}).then(function () {
return db.put({_id: 'foobar'});
})
]);
}).then(function (result) {
var change = result[0];
change.id.should.equal('foobar');
change.doc._id.should.equal('foobar');
});
});
it('Kill database while listening to live changes', function (done) {
var db = new PouchDB(dbs.name);
db.changes({live: true})
.on('error', function () { done(); })
.on('complete', function () { done(); })
.on('change', function () { db.destroy().catch(done); });
db.post({ test: 'adoc' });
});
it('#3136 style=all_docs', function () {
var db = new PouchDB(dbs.name);
var chain = PouchDB.utils.Promise.resolve();
var docIds = ['b', 'c', 'a', 'z', 'd', 'e'];
docIds.forEach(function (docId) {
chain = chain.then(function () {
return db.put({_id: docId});
});
});
return chain.then(function () {
return db.changes({style: 'all_docs'});
}).then(function (res) {
var ids = res.results.map(function (x) {
return x.id;
});
ids.should.include.members(docIds);
});
});
it('#4191 revs_diff causes endless loop', function () {
var db = new PouchDB(dbs.name, {auto_compaction: false});
return db.bulkDocs({
"new_edits": false,
"docs": [{"_id": "799","_rev":"1-d22"}
]}).then(function () {
return db.bulkDocs({
"new_edits": false,
"docs": [{"_id": "3E1", "_rev": "1-ab5"}]
});
}).then(function () {
return db.bulkDocs(
{ new_edits: false,
docs:
[ { _id: 'FB3', _rev: '1-363' },
{ _id: '27C', _rev: '1-4c3' },
{ _id: 'BD6', _rev: '1-de0' },
{ _id: '1E9', _rev: '1-451' } ] }
);
}).then(function () {
return db.changes({style: 'all_docs', limit: 100});
}).then(function (res) {
var lastSeq = res.last_seq;
return db.changes({since: lastSeq, style: 'all_docs', limit: 100});
}).then(function (res) {
res.results.should.have.length(0);
});
});
it('#3136 style=all_docs & include_docs', function () {
var db = new PouchDB(dbs.name);
var chain = PouchDB.utils.Promise.resolve();
var docIds = ['b', 'c', 'a', 'z', 'd', 'e'];
docIds.forEach(function (docId) {
chain = chain.then(function () {
return db.put({_id: docId});
});
});
return chain.then(function () {
return db.changes({
style: 'all_docs',
include_docs: true
});
}).then(function (res) {
var ids = res.results.map(function (x) {
return x.id;
});
ids.should.include.members(docIds);
});
});
it('#3136 tricky changes, limit/descending', function () {
if (testUtils.isCouchMaster()) {
return true;
}
var db = new PouchDB(dbs.name);
var docs = [
{
_id: 'alpha',
_rev: '1-a',
_revisions: {
start: 1,
ids: ['a']
}
}, {
_id: 'beta',
_rev: '1-b',
_revisions: {
start: 1,
ids: ['b']
}
}, {
_id: 'gamma',
_rev: '1-b',
_revisions: {
start: 1,
ids: ['b']
}
}, {
_id: 'alpha',
_rev: '2-d',
_revisions: {
start: 2,
ids: ['d', 'a']
}
}, {
_id: 'beta',
_rev: '2-e',
_revisions: {
start: 2,
ids: ['e', 'b']
}
}, {
_id: 'beta',
_rev: '3-f',
_deleted: true,
_revisions: {
start: 3,
ids: ['f', 'e', 'b']
}
}
];
var chain = PouchDB.utils.Promise.resolve();
var seqs = [];
docs.forEach(function (doc) {
chain = chain.then(function () {
return db.bulkDocs([doc], {new_edits: false}).then(function () {
return db.changes({doc_ids: [doc._id]});
}).then(function (res) {
seqs.push(res.results[0].seq);
});
});
});
function normalizeResult(result) {
// order of changes doesn't matter
result.results.forEach(function (ch) {
ch.changes = ch.changes.sort(function (a, b) {
return a.rev < b.rev ? -1 : 1;
});
});
}
return chain.then(function () {
return db.changes();
}).then(function (result) {
normalizeResult(result);
result.should.deep.equal({
"results": [
{
"seq": seqs[2],
"id": "gamma",
"changes": [{ "rev": "1-b"}
]
},
{
"seq": seqs[3],
"id": "alpha",
"changes": [{ "rev": "2-d"}
]
},
{
"seq": seqs[5],
"id": "beta",
"deleted": true,
"changes": [{ "rev": "3-f"}
]
}
],
"last_seq": seqs[5]
});
return db.changes({limit: 0});
}).then(function (result) {
normalizeResult(result);
result.should.deep.equal({
"results": [{
"seq": seqs[2],
"id": "gamma",
"changes": [{"rev": "1-b"}]
}],
"last_seq": seqs[2]
}, '1:' + JSON.stringify(result));
return db.changes({limit: 1});
}).then(function (result) {
normalizeResult(result);
result.should.deep.equal({
"results": [{
"seq": seqs[2],
"id": "gamma",
"changes": [{"rev": "1-b"}]
}],
"last_seq": seqs[2]
}, '2:' + JSON.stringify(result));
return db.changes({limit: 2});
}).then(function (result) {
normalizeResult(result);
result.should.deep.equal({
"results": [{
"seq": seqs[2],
"id": "gamma",
"changes": [{"rev": "1-b"}]
}, {"seq": seqs[3], "id": "alpha", "changes": [{"rev": "2-d"}]}],
"last_seq": seqs[3]
}, '3:' + JSON.stringify(result));
return db.changes({limit: 1, descending: true});
}).then(function (result) {
normalizeResult(result);
result.should.deep.equal({
"results": [{
"seq": seqs[5],
"id": "beta",
"changes": [{"rev": "3-f"}],
"deleted": true
}],
"last_seq": seqs[5]
}, '4:' + JSON.stringify(result));
return db.changes({limit: 2, descending: true});
}).then(function (result) {
normalizeResult(result);
var expected = {
"results": [{
"seq": seqs[5],
"id": "beta",
"changes": [{"rev": "3-f"}],
"deleted": true
}, {"seq": seqs[3], "id": "alpha", "changes": [{"rev": "2-d"}]}],
"last_seq": seqs[3]
};
result.should.deep.equal(expected, '5:' + JSON.stringify(result) +
', shoulda got: ' + JSON.stringify(expected));
return db.changes({descending: true});
}).then(function (result) {
normalizeResult(result);
var expected = {
"results": [{
"seq": seqs[5],
"id": "beta",
"changes": [{"rev": "3-f"}],
"deleted": true
}, {"seq": seqs[3], "id": "alpha", "changes": [{"rev": "2-d"}]}, {
"seq": seqs[2],
"id": "gamma",
"changes": [{"rev": "1-b"}]
}],
"last_seq": seqs[2]
};
result.should.deep.equal(expected, '6:' + JSON.stringify(result) +
', shoulda got: ' + JSON.stringify(expected));
});
});
it('#3176 winningRev has a lower seq, descending', function () {
if (testUtils.isCouchMaster()) {
return true;
}
var db = new PouchDB(dbs.name);
var tree = [
[
{
_id: 'foo',
_rev: '1-a',
_revisions: {start: 1, ids: ['a']}
},
{
_id: 'foo',
_rev: '2-e',
_deleted: true,
_revisions: {start: 2, ids: ['e', 'a']}
},
{
_id: 'foo',
_rev: '3-g',
_revisions: {start: 3, ids: ['g', 'e', 'a']}
}
],
[
{
_id: 'foo',
_rev: '1-a',
_revisions: {start: 1, ids: ['a']}
},
{
_id: 'foo',
_rev: '2-b',
_revisions: {start: 2, ids: ['b', 'a']}
},
{
_id: 'foo',
_rev: '3-c',
_revisions: {start: 3, ids: ['c', 'b', 'a']}
}
]
];
var chain = PouchDB.utils.Promise.resolve();
var seqs = [0];
function getExpected(i) {
var expecteds = [
{
"results": [
{
"seq": seqs[1],
"id": "foo",
"changes": [{"rev": "3-g"}]
}
],
"last_seq" : seqs[1]
},
{
"results": [
{
"seq": seqs[2],
"id": "foo",
"changes": [{"rev": "3-g"}]
}
],
"last_seq" : seqs[2]
}
];
return expecteds[i];
}
function normalizeResult(result) {
// order of changes doesn't matter
result.results.forEach(function (ch) {
ch.changes = ch.changes.sort(function (a, b) {
return a.rev < b.rev ? -1 : 1;
});
});
}
tree.forEach(function (docs, i) {
chain = chain.then(function () {
return db.bulkDocs(docs, {new_edits: false}).then(function () {
return db.changes({
descending: true
});
}).then(function (result) {
seqs.push(result.last_seq);
var expected = getExpected(i);
normalizeResult(result);
result.should.deep.equal(expected,
i + ': should get: ' + JSON.stringify(expected) +
', but got: ' + JSON.stringify(result));
});
});
});
return chain;
});
it('#3136 winningRev has a lower seq, style=all_docs', function () {
if (testUtils.isCouchMaster()) {
return true;
}
var db = new PouchDB(dbs.name);
var tree = [
[
{
_id: 'foo',
_rev: '1-a',
_revisions: {start: 1, ids: ['a']}
},
{
_id: 'foo',
_rev: '2-e',
_deleted: true,
_revisions: {start: 2, ids: ['e', 'a']}
},
{
_id: 'foo',
_rev: '3-g',
_revisions: {start: 3, ids: ['g', 'e', 'a']}
}
],
[
{
_id: 'foo',
_rev: '1-a',
_revisions: {start: 1, ids: ['a']}
},
{
_id: 'foo',
_rev: '2-b',
_revisions: {start: 2, ids: ['b', 'a']}
},
{
_id: 'foo',
_rev: '3-c',
_revisions: {start: 3, ids: ['c', 'b', 'a']}
}
],
[
{
_id: 'foo',
_rev: '1-a',
_revisions: {start: 1, ids: ['a']}
},
{
_id: 'foo',
_rev: '2-d',
_revisions: {start: 2, ids: ['d', 'a']}
},
{
_id: 'foo',
_rev: '3-h',
_revisions: {start: 3, ids: ['h', 'd', 'a']}
},
{
_id: 'foo',
_rev: '4-f',
_revisions: {start: 4, ids: ['f', 'h', 'd', 'a']}
}
]
];
var chain = PouchDB.utils.Promise.resolve();
var seqs = [0];
function getExpected(i) {
var expecteds = [
{
"results": [
{
"seq": seqs[1],
"id": "foo",
"changes": [{"rev": "3-g"}],
"doc": {"_id": "foo", "_rev": "3-g"}
}
],
"last_seq" : seqs[1]
},
{
"results": [
{
"seq": seqs[2],
"id": "foo",
"changes": [{"rev": "3-c"}, {"rev": "3-g"}],
"doc": {"_id": "foo", "_rev": "3-g"}
}
],
"last_seq" : seqs[2]
},
{
"results": [
{
"seq": seqs[3],
"id": "foo",
"changes": [{"rev": "3-c"}, {"rev": "3-g"}, {"rev": "4-f"}],
"doc": {"_id": "foo", "_rev": "4-f"}
}
],
"last_seq" : seqs[3]
}
];
return expecteds[i];
}
function normalizeResult(result) {
// order of changes doesn't matter
result.results.forEach(function (ch) {
ch.changes = ch.changes.sort(function (a, b) {
return a.rev < b.rev ? -1 : 1;
});
});
}
tree.forEach(function (docs, i) {
chain = chain.then(function () {
return db.bulkDocs(docs, {new_edits: false}).then(function () {
return db.changes({
style: 'all_docs',
since: seqs[seqs.length - 1],
include_docs: true
});
}).then(function (result) {
seqs.push(result.last_seq);
var expected = getExpected(i);
normalizeResult(result);
result.should.deep.equal(expected,
i + ': should get: ' + JSON.stringify(expected) +
', but got: ' + JSON.stringify(result));
});
});
});
return chain;
});
it('#3136 winningRev has a lower seq, style=all_docs 2', function () {
if (testUtils.isCouchMaster()) {
return true;
}
var db = new PouchDB(dbs.name);
var tree = [
[
{
_id: 'foo',
_rev: '1-a',
_revisions: {start: 1, ids: ['a']}
},
{
_id: 'foo',
_rev: '2-e',
_deleted: true,
_revisions: {start: 2, ids: ['e', 'a']}
},
{
_id: 'foo',
_rev: '3-g',
_revisions: {start: 3, ids: ['g', 'e', 'a']}
}
], [
{
_id: 'foo',
_rev: '1-a',
_revisions: {start: 1, ids: ['a']}
},
{
_id: 'foo',
_rev: '2-b',
_revisions: {start: 2, ids: ['b', 'a']}
},
{
_id: 'foo',
_rev: '3-c',
_revisions: {start: 3, ids: ['c', 'b', 'a']}
}
], [
{
_id: 'bar',
_rev: '1-z',
_revisions: {start: 1, ids: ['z']}
}
]
];
var chain = PouchDB.utils.Promise.resolve();
var seqs = [0];
tree.forEach(function (docs) {
chain = chain.then(function () {
return db.bulkDocs(docs, {new_edits: false}).then(function () {
return db.changes();
}).then(function (result) {
seqs.push(result.last_seq);
});
});
});
return chain.then(function () {
var expecteds = [
{
"results": [{
"seq": seqs[2],
"id": "foo",
"changes": [{"rev": "3-c"}, {"rev": "3-g"}]
}, {"seq": seqs[3], "id": "bar", "changes": [{"rev": "1-z"}]}],
"last_seq": seqs[3]
},
{
"results": [{
"seq": seqs[2],
"id": "foo",
"changes": [{"rev": "3-c"}, {"rev": "3-g"}]
}, {"seq": seqs[3], "id": "bar", "changes": [{"rev": "1-z"}]}],
"last_seq": seqs[3]
},
{
"results": [{"seq": seqs[3], "id": "bar",
"changes": [{"rev": "1-z"}]}],
"last_seq": seqs[3]
},
{"results": [], "last_seq": seqs[3]}
];
var chain2 = PouchDB.utils.Promise.resolve();
function normalizeResult(result) {
// order of changes doesn't matter
result.results.forEach(function (ch) {
ch.changes = ch.changes.sort(function (a, b) {
return a.rev < b.rev ? -1 : 1;
});
});
}
seqs.forEach(function (seq, i) {
chain2 = chain2.then(function () {
return db.changes({
since: seq,
style: 'all_docs'
}).then(function (res) {
normalizeResult(res);
res.should.deep.equal(expecteds[i], 'since=' + seq +
': got: ' +
JSON.stringify(res) +
', shoulda got: ' +
JSON.stringify(expecteds[i]));
});
});
});
return chain2;
});
});
it('#3136 winningRev has a higher seq, using limit', function () {
if (testUtils.isCouchMaster()) {
return true;
}
var db = new PouchDB(dbs.name);
var tree = [
[
{
_id: 'foo',
_rev: '1-a',
_revisions: {start: 1, ids: ['a']}
}
], [
{
_id: 'foo',
_rev: '2-b',
_revisions: {start: 2, ids: ['b', 'a']}
}
], [
{
_id: 'bar',
_rev: '1-x',
_revisions: {start: 1, ids: ['x']}
}
], [
{
_id: 'foo',
_rev: '2-c',
_deleted: true,
_revisions: {start: 2, ids: ['c', 'a']}
}
]
];
var chain = PouchDB.utils.Promise.resolve();
var seqs = [0];
tree.forEach(function (docs) {
chain = chain.then(function () {
return db.bulkDocs(docs, {new_edits: false}).then(function () {
return db.changes().then(function (result) {
seqs.push(result.last_seq);
});
});
});
});
return chain.then(function () {
var expecteds = [{
"results": [{
"seq": seqs[3],
"id": "bar",
"changes": [{"rev": "1-x"}],
"doc": {"_id": "bar", "_rev": "1-x"}
}],
"last_seq": seqs[3]
},
{
"results": [{
"seq": seqs[3],
"id": "bar",
"changes": [{"rev": "1-x"}],
"doc": {"_id": "bar", "_rev": "1-x"}
}],
"last_seq": seqs[3]
},
{
"results": [{
"seq": seqs[3],
"id": "bar",
"changes": [{"rev": "1-x"}],
"doc": {"_id": "bar", "_rev": "1-x"}
}],
"last_seq": seqs[3]
},
{
"results": [{
"seq": seqs[4],
"id": "foo",
"changes": [{"rev": "2-b"}, {"rev": "2-c"}],
"doc": {"_id": "foo", "_rev": "2-b"}
}],
"last_seq": seqs[4]
},
{"results": [], "last_seq": seqs[4]}
];
var chain2 = PouchDB.utils.Promise.resolve();
function normalizeResult(result) {
// order of changes doesn't matter
result.results.forEach(function (ch) {
ch.changes = ch.changes.sort(function (a, b) {
return a.rev < b.rev ? -1 : 1;
});
});
}
seqs.forEach(function (seq, i) {
chain2 = chain2.then(function () {
return db.changes({
style: 'all_docs',
since: seq,
limit: 1,
include_docs: true
});
}).then(function (result) {
normalizeResult(result);
result.should.deep.equal(expecteds[i],
i + ': got: ' + JSON.stringify(result) +
', shoulda got: ' + JSON.stringify(expecteds[i]));
});
});
return chain2;
});
});
it('changes-filter', function (done) {
var docs1 = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2},
{_id: '3', integer: 3}
];
var docs2 = [
{_id: '4', integer: 4},
{_id: '5', integer: 5},
{_id: '6', integer: 6},
{_id: '7', integer: 7}
];
var db = new PouchDB(dbs.name);
var count = 0;
db.bulkDocs({ docs: docs1 }, function () {
var changes = db.changes({
filter: function (doc) {
return doc.integer % 2 === 0;
},
live: true
}).on('complete', function (result) {
result.status.should.equal('cancelled');
done();
}).on('change', function () {
count += 1;
if (count === 4) {
changes.cancel();
}
}).on('error', done);
db.bulkDocs({ docs: docs2 });
});
});
it('changes-filter with query params', function (done) {
var docs1 = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2},
{_id: '3', integer: 3}
];
var docs2 = [
{_id: '4', integer: 4},
{_id: '5', integer: 5},
{_id: '6', integer: 6},
{_id: '7', integer: 7}
];
var params = { 'abc': true };
var db = new PouchDB(dbs.name);
var count = 0;
db.bulkDocs({ docs: docs1 }, function () {
var changes = db.changes({
filter: function (doc, req) {
if (req.query.abc) {
return doc.integer % 2 === 0;
}
},
query_params: params,
live: true
}).on('complete', function (result) {
result.status.should.equal('cancelled');
done();
}).on('change', function () {
count += 1;
if (count === 4) {
changes.cancel();
}
}).on('error', done);
db.bulkDocs({ docs: docs2 });
});
});
it('Non-live changes filter', function (done) {
var docs1 = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2},
{_id: '3', integer: 3}
];
var db = new PouchDB(dbs.name);
db.bulkDocs({ docs: docs1 }, function () {
db.changes().on('complete', function (allChanges) {
db.changes({
filter: function (doc) {
return doc.integer % 2 === 0;
}
}).on('complete', function (filteredChanges) {
// Should get docs 0 and 2 if the filter
// has been applied correctly.
filteredChanges.results.length.should.equal(2);
filteredChanges.last_seq.should.deep.equal(allChanges.last_seq);
done();
}).on('error', done);
}).on('error', done);
});
});
it('Non-live changes filter, descending', function (done) {
var docs1 = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2},
{_id: '3', integer: 3}
];
var db = new PouchDB(dbs.name);
db.bulkDocs({ docs: docs1 }, function () {
db.changes({
descending: true
}).on('complete', function (allChanges) {
db.changes({
descending: true,
filter: function (doc) {
return doc.integer > 2;
}
}).on('complete', function (filteredChanges) {
// Should get docs 2 and 3 if the filter
// has been applied correctly.
filteredChanges.results.length.should.equal(1);
filteredChanges.last_seq.should.deep.equal(allChanges.last_seq);
done();
}).on('error', done);
}).on('error', done);
});
});
it('#2569 Non-live doc_ids filter', function () {
var docs = [
{_id: '0'},
{_id: '1'},
{_id: '2'},
{_id: '3'}
];
var db = new PouchDB(dbs.name);
return db.bulkDocs(docs).then(function () {
return db.changes({
doc_ids: ['1', '3']
});
}).then(function (changes) {
var ids = changes.results.map(function (x) {
return x.id;
});
ids.sort().should.deep.equal(['1', '3']);
});
});
it('#2569 Big non-live doc_ids filter', function () {
var docs = [];
for (var i = 0; i < 5; i++) {
var id = '';
for (var j = 0; j < 50; j++) {
// make a huge id
id += PouchDB.utils.btoa(Math.random().toString());
}
docs.push({_id: id});
}
var db = new PouchDB(dbs.name);
return db.bulkDocs(docs).then(function () {
return db.changes({
doc_ids: [docs[1]._id, docs[3]._id]
});
}).then(function (changes) {
var ids = changes.results.map(function (x) {
return x.id;
});
var expectedIds = [docs[1]._id, docs[3]._id];
ids.sort().should.deep.equal(expectedIds.sort());
});
});
it('#2569 Live doc_ids filter', function () {
var docs = [
{_id: '0'},
{_id: '1'},
{_id: '2'},
{_id: '3'}
];
var db = new PouchDB(dbs.name);
return db.bulkDocs(docs).then(function () {
return new PouchDB.utils.Promise(function (resolve, reject) {
var retChanges = [];
var changes = db.changes({
doc_ids: ['1', '3'],
live: true
}).on('change', function (change) {
retChanges.push(change);
if (retChanges.length === 2) {
changes.cancel();
resolve(retChanges);
}
}).on('error', reject);
});
}).then(function (changes) {
var ids = changes.map(function (x) {
return x.id;
});
var expectedIds = ['1', '3'];
ids.sort().should.deep.equal(expectedIds);
});
});
it('#2569 Big live doc_ids filter', function () {
var docs = [];
for (var i = 0; i < 5; i++) {
var id = '';
for (var j = 0; j < 50; j++) {
// make a huge id
id += PouchDB.utils.btoa(Math.random().toString());
}
docs.push({_id: id});
}
var db = new PouchDB(dbs.name);
return db.bulkDocs(docs).then(function () {
return new PouchDB.utils.Promise(function (resolve, reject) {
var retChanges = [];
var changes = db.changes({
doc_ids: [docs[1]._id, docs[3]._id],
live: true
}).on('change', function (change) {
retChanges.push(change);
if (retChanges.length === 2) {
changes.cancel();
resolve(retChanges);
}
}).on('error', reject);
});
}).then(function (changes) {
var ids = changes.map(function (x) {
return x.id;
});
var expectedIds = [docs[1]._id, docs[3]._id];
ids.sort().should.deep.equal(expectedIds.sort());
});
});
it('#2569 Non-live doc_ids filter with filter=_doc_ids', function () {
var docs = [
{_id: '0'},
{_id: '1'},
{_id: '2'},
{_id: '3'}
];
var db = new PouchDB(dbs.name);
return db.bulkDocs(docs).then(function () {
return db.changes({
filter: '_doc_ids',
doc_ids: ['1', '3']
});
}).then(function (changes) {
var ids = changes.results.map(function (x) {
return x.id;
});
ids.sort().should.deep.equal(['1', '3']);
});
});
it('#2569 Live doc_ids filter with filter=_doc_ids', function () {
var docs = [
{_id: '0'},
{_id: '1'},
{_id: '2'},
{_id: '3'}
];
var db = new PouchDB(dbs.name);
return db.bulkDocs(docs).then(function () {
return db.changes({
filter: '_doc_ids',
doc_ids: ['1', '3']
});
}).then(function (changes) {
var ids = changes.results.map(function (x) {
return x.id;
});
ids.sort().should.deep.equal(['1', '3']);
});
});
it('Changes to same doc are grouped', function (done) {
var docs1 = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2},
{_id: '3', integer: 3}
];
var docs2 = [
{_id: '2', integer: 11},
{_id: '3', integer: 12}
];
var db = new PouchDB(dbs.name);
db.bulkDocs({ docs: docs1 }, function (err, info) {
docs2[0]._rev = info[2].rev;
docs2[1]._rev = info[3].rev;
db.put(docs2[0], function () {
db.put(docs2[1], function () {
db.changes({
include_docs: true
}).on('complete', function (changes) {
changes.results.length.should.equal(4);
var second = findById(changes.results, '2');
second.changes.length.should.equal(1);
second.doc.integer.should.equal(11);
done();
}).on('error', done);
});
});
});
});
it('Changes with conflicts are handled correctly', function (testDone) {
var docs1 = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2},
{_id: '3', integer: 3}
];
var docs2 = [
{_id: '2', integer: 11},
{_id: '3', integer: 12}
];
new PouchDB(dbs.name).then(function (localdb) {
var remotedb = new PouchDB(dbs.remote);
return localdb.bulkDocs({ docs: docs1 }).then(function (info) {
docs2[0]._rev = info[2].rev;
docs2[1]._rev = info[3].rev;
return localdb.put(docs2[0]).then(function () {
return localdb.put(docs2[1]).then(function (info) {
var rev2 = info.rev;
return PouchDB.replicate(localdb, remotedb).then(function () {
// update remote once, local twice, then replicate from
// remote to local so the remote losing conflict is later in
// the tree
return localdb.put({
_id: '3',
_rev: rev2,
integer: 20
}).then(function (resp) {
var rev3Doc = {
_id: '3',
_rev: resp.rev,
integer: 30
};
return localdb.put(rev3Doc).then(function (resp) {
var rev4local = resp.rev;
var rev4Doc = {
_id: '3',
_rev: rev2,
integer: 100
};
return remotedb.put(rev4Doc).then(function (resp) {
var remoterev = resp.rev;
return PouchDB.replicate(remotedb, localdb).then(
function () {
return localdb.changes({
include_docs: true,
style: 'all_docs',
conflicts: true
}).on('error', testDone)
.then(function (changes) {
changes.results.length.should.equal(4);
var ch = findById(changes.results, '3');
ch.changes.length.should.equal(2);
ch.doc.integer.should.equal(30);
ch.doc._rev.should.equal(rev4local);
ch.changes.should.deep.equal([
{ rev: rev4local },
{ rev: remoterev }
]);
ch.doc.should.have.property('_conflicts');
ch.doc._conflicts.length.should.equal(1);
ch.doc._conflicts[0].should.equal(remoterev);
});
});
});
});
});
});
});
});
}).then(function () {
testDone();
}, testDone);
});
});
it('Change entry for a deleted doc', function (done) {
var docs1 = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2},
{_id: '3', integer: 3}
];
var db = new PouchDB(dbs.name);
db.bulkDocs({ docs: docs1 }, function (err, info) {
var rev = info[3].rev;
db.remove({
_id: '3',
_rev: rev
}, function () {
db.changes({
include_docs: true
}).on('complete', function (changes) {
changes.results.length.should.equal(4);
var ch = findById(changes.results, '3');
// sequence numbers are not incremental in CouchDB 2.0
if (!testUtils.isCouchMaster()) {
ch.seq.should.equal(5);
}
ch.deleted.should.equal(true);
done();
}).on('error', done);
});
});
});
it('changes large number of docs', function (done) {
var docs = [];
var num = 30;
for (var i = 0; i < num; i++) {
docs.push({
_id: 'doc_' + i,
foo: 'bar_' + i
});
}
var db = new PouchDB(dbs.name);
db.bulkDocs({ docs: docs }, function () {
db.changes().on('complete', function (res) {
res.results.length.should.equal(num);
done();
}).on('error', done);
});
});
it('Calling db.changes({since: \'now\'})', function (done) {
var db = new PouchDB(dbs.name);
db.bulkDocs({ docs: [{ foo: 'bar' }] }, function () {
db.info(function (err, info) {
var api = db.changes({
since: 'now'
}).on('complete', function (res) {
// last_seq and update_seq might be encoded differently
// in clustered CouchDB - they cannot be reliably compared.
if (!testUtils.isCouchMaster()) {
res.last_seq.should.equal(info.update_seq);
}
done();
}).on('error', done);
api.should.be.an('object');
api.cancel.should.be.an('function');
});
});
});
//Duplicate to make sure both api options work.
it('Calling db.changes({since: \'latest\'})', function (done) {
var db = new PouchDB(dbs.name);
db.bulkDocs({ docs: [{ foo: 'bar' }] }, function () {
db.info(function (err, info) {
var api = db.changes({
since: 'latest'
}).on('complete', function (res) {
// last_seq and update_seq might be encoded differently
// in clustered CouchDB - they cannot be reliably compared.
if (!testUtils.isCouchMaster()) {
res.last_seq.should.equal(info.update_seq);
}
done();
}).on('error', done);
api.should.be.an('object');
api.cancel.should.be.an('function');
});
});
});
it('Closing db does not cause a crash if changes cancelled',
function (done) {
var db = new PouchDB(dbs.name);
var called = 0;
function checkDone() {
called++;
if (called === 2) {
done();
}
}
db.bulkDocs({ docs: [{ foo: 'bar' }] }, function () {
var changes = db.changes({
live: true
}).on('complete', function (result) {
result.status.should.equal('cancelled');
checkDone();
});
should.exist(changes);
changes.cancel.should.be.a('function');
changes.cancel();
db.close(function (error) {
should.not.exist(error);
checkDone();
});
});
});
it('fire-complete-on-cancel', function (done) {
var db = new PouchDB(dbs.name);
var cancelled = false;
var changes = db.changes({
live: true
}).on('complete', function (result) {
cancelled.should.equal(true);
should.exist(result);
if (result) {
result.status.should.equal('cancelled');
}
done();
}).on('error', done);
should.exist(changes);
changes.cancel.should.be.a('function');
setTimeout(function () {
cancelled = true;
changes.cancel();
}, 100);
});
it('changes are not duplicated', function (done) {
var db = new PouchDB(dbs.name);
var called = 0;
var changes = db.changes({
live: true
}).on('change', function () {
called++;
if (called === 1) {
setTimeout(function () {
changes.cancel();
}, 1000);
}
}).on('complete', function () {
called.should.equal(1);
done();
});
db.post({key: 'value'});
});
it('supports return_docs=false', function (done) {
var db = new PouchDB(dbs.name);
var docs = [];
var num = 10;
for (var i = 0; i < num; i++) {
docs.push({ _id: 'doc_' + i});
}
var changes = 0;
db.bulkDocs({ docs: docs }, function (err) {
if (err) {
return done(err);
}
db.changes({
descending: true,
return_docs: false
}).on('change', function () {
changes++;
}).on('complete', function (results) {
results.results.should.have.length(0, '0 results returned');
changes.should.equal(num, 'correct number of changes');
done();
}).on('error', function (err) {
done(err);
});
});
});
// TODO: Remove 'returnDocs' in favor of 'return_docs' in a future release
it('supports returnDocs=false', function (done) {
var db = new PouchDB(dbs.name);
var docs = [];
var num = 10;
for (var i = 0; i < num; i++) {
docs.push({ _id: 'doc_' + i});
}
var changes = 0;
db.bulkDocs({ docs: docs }, function (err) {
if (err) {
return done(err);
}
db.changes({
descending: true,
returnDocs: false
}).on('change', function () {
changes++;
}).on('complete', function (results) {
results.results.should.have.length(0, '0 results returned');
changes.should.equal(num, 'correct number of changes');
done();
}).on('error', function (err) {
done(err);
});
});
});
it('should respects limit', function (done) {
var docs1 = [
{_id: '_local/foo'},
{_id: 'a', integer: 0},
{_id: 'b', integer: 1},
{_id: 'c', integer: 2},
{_id: 'd', integer: 3}
];
var called = 0;
var db = new PouchDB(dbs.name);
db.bulkDocs({ docs: docs1 }, function () {
db.changes({
limit: 1
}).on('change', function () {
(called++).should.equal(0);
}).on('complete', function () {
setTimeout(function () {
done();
}, 50);
});
});
});
it('doesn\'t throw if opts.complete is undefined', function (done) {
var db = new PouchDB(dbs.name);
db.put({_id: 'foo'}).then(function () {
db.changes().on('change', function () {
done();
}).on('error', function (err) {
done(err);
});
}, done);
});
it('it handles a bunch of individual changes in live replication',
function (done) {
var db = new PouchDB(dbs.name);
var len = 80;
var called = 0;
var changesDone = false;
var changesWritten = 0;
var changes = db.changes({live: true});
changes.on('change', function () {
called++;
if (called === len) {
changes.cancel();
}
}).on('error', done).on('complete', function () {
changesDone = true;
maybeDone();
});
var i = -1;
function maybeDone() {
if (changesDone && changesWritten === len) {
done();
}
}
function after() {
changesWritten++;
db.listeners('destroyed').should.have.length.lessThan(5);
maybeDone();
}
while (++i < len) {
db.post({}).then(after).catch(done);
}
});
it('changes-filter without filter', function (done) {
var docs1 = [
{_id: '0', integer: 0},
{_id: '1', integer: 1},
{_id: '2', integer: 2},
{_id: '3', integer: 3}
];
var docs2 = [
{_id: '4', integer: 4},
{_id: '5', integer: 5},
{_id: '6', integer: 6},
{_id: '7', integer: 7}
];
var db = new PouchDB(dbs.name);
var count = 0;
db.bulkDocs({ docs: docs1 }, function () {
var changes = db.changes({
live: true
}).on('complete', function (result) {
result.status.should.equal('cancelled');
done();
}).on('change', function () {
count += 1;
if (count === 8) {
changes.cancel();
}
}).on('error', done);
db.bulkDocs({ docs: docs2 });
});
});
});
});
describe('changes-standalone', function () {
it('Changes reports errors', function (done) {
var db = new PouchDB('http://infiniterequest.com', { skipSetup: true });
db.changes({
timeout: 1000
}).on('error', function (err) {
should.exist(err);
done();
});
});
});