mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
324 lines
9.9 KiB
TypeScript
324 lines
9.9 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2017 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import { Timestamp as TimestampInstance } from '@firebase/firestore-types';
|
|
import { expect } from 'chai';
|
|
|
|
import {
|
|
collection,
|
|
doc,
|
|
documentId,
|
|
endAt,
|
|
endBefore,
|
|
getDoc,
|
|
getDocs,
|
|
limit,
|
|
orderBy,
|
|
query,
|
|
setDoc,
|
|
startAfter,
|
|
startAt,
|
|
Timestamp,
|
|
where
|
|
} from '../util/firebase_export';
|
|
import {
|
|
apiDescribe,
|
|
toDataArray,
|
|
toIds,
|
|
withTestCollection,
|
|
withTestDb,
|
|
withTestDbs
|
|
} from '../util/helpers';
|
|
|
|
apiDescribe('Cursors', persistence => {
|
|
it('can page through items', () => {
|
|
const testDocs = {
|
|
a: { v: 'a' },
|
|
b: { v: 'b' },
|
|
c: { v: 'c' },
|
|
d: { v: 'd' },
|
|
e: { v: 'e' },
|
|
f: { v: 'f' }
|
|
};
|
|
return withTestCollection(persistence, testDocs, coll => {
|
|
return getDocs(query(coll, limit(2)))
|
|
.then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([{ v: 'a' }, { v: 'b' }]);
|
|
const lastDoc = docs.docs[docs.docs.length - 1];
|
|
return getDocs(query(coll, limit(3), startAfter(lastDoc)));
|
|
})
|
|
.then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([
|
|
{ v: 'c' },
|
|
{ v: 'd' },
|
|
{ v: 'e' }
|
|
]);
|
|
const lastDoc = docs.docs[docs.docs.length - 1];
|
|
return getDocs(query(coll, limit(1), startAfter(lastDoc)));
|
|
})
|
|
.then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([{ v: 'f' }]);
|
|
const lastDoc = docs.docs[docs.docs.length - 1];
|
|
return getDocs(query(coll, limit(3), startAfter(lastDoc)));
|
|
})
|
|
.then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([]);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be created from documents', () => {
|
|
const testDocs = {
|
|
a: { k: 'a', sort: 1 },
|
|
b: { k: 'b', sort: 2 },
|
|
c: { k: 'c', sort: 2 },
|
|
d: { k: 'd', sort: 2 },
|
|
e: { k: 'e', sort: 0 },
|
|
f: { k: 'f', nosort: 1 } // should not show up
|
|
};
|
|
return withTestCollection(persistence, testDocs, coll => {
|
|
const sortedQuery = query(coll, orderBy('sort'));
|
|
return getDoc(doc(coll, 'c'))
|
|
.then(doc => {
|
|
expect(doc.data()).to.deep.equal({ k: 'c', sort: 2 });
|
|
return getDocs(query(sortedQuery, startAt(doc))).then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([
|
|
{ k: 'c', sort: 2 },
|
|
{ k: 'd', sort: 2 }
|
|
]);
|
|
return getDocs(query(sortedQuery, endBefore(doc)));
|
|
});
|
|
})
|
|
.then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([
|
|
{ k: 'e', sort: 0 },
|
|
{ k: 'a', sort: 1 },
|
|
{ k: 'b', sort: 2 }
|
|
]);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be created from values', () => {
|
|
const testDocs = {
|
|
a: { k: 'a', sort: 1 },
|
|
b: { k: 'b', sort: 2 },
|
|
c: { k: 'c', sort: 2 },
|
|
d: { k: 'd', sort: 2 },
|
|
e: { k: 'e', sort: 0 },
|
|
f: { k: 'f', nosort: 1 } // should not show up
|
|
};
|
|
return withTestCollection(persistence, testDocs, coll => {
|
|
const sortedQuery = query(coll, orderBy('sort'));
|
|
return getDocs(query(sortedQuery, startAt(2)))
|
|
.then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([
|
|
{ k: 'b', sort: 2 },
|
|
{ k: 'c', sort: 2 },
|
|
{ k: 'd', sort: 2 }
|
|
]);
|
|
return getDocs(query(sortedQuery, endBefore(2)));
|
|
})
|
|
.then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([
|
|
{ k: 'e', sort: 0 },
|
|
{ k: 'a', sort: 1 }
|
|
]);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be created using document id', () => {
|
|
const testDocs: { [key: string]: {} } = {
|
|
a: { k: 'a' },
|
|
b: { k: 'b' },
|
|
c: { k: 'c' },
|
|
d: { k: 'd' },
|
|
e: { k: 'e' }
|
|
};
|
|
return withTestDbs(persistence, 2, ([reader, writer]) => {
|
|
// Create random subcollection with documents pre-filled. Note that
|
|
// we use subcollections to test the relative nature of __id__.
|
|
const writerCollection = collection(
|
|
doc(collection(writer, 'parent-collection')),
|
|
'sub-collection'
|
|
);
|
|
const readerCollection = collection(reader, writerCollection.path);
|
|
const sets: Array<Promise<void>> = [];
|
|
Object.keys(testDocs).forEach((key: string) => {
|
|
sets.push(setDoc(doc(writerCollection, key), testDocs[key]));
|
|
});
|
|
|
|
return Promise.all(sets)
|
|
.then(() =>
|
|
getDocs(
|
|
query(
|
|
readerCollection,
|
|
orderBy(documentId()),
|
|
startAt('b'),
|
|
endBefore('d')
|
|
)
|
|
)
|
|
)
|
|
.then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([{ k: 'b' }, { k: 'c' }]);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be used with reference values', () => {
|
|
// We require a db to create reference values
|
|
return withTestDb(persistence, db => {
|
|
const testDocs = {
|
|
a: { k: '1a', ref: doc(db, '1', 'a') },
|
|
b: { k: '1b', ref: doc(db, '1', 'b') },
|
|
c: { k: '2a', ref: doc(db, '2', 'a') },
|
|
d: { k: '2b', ref: doc(db, '2', 'b') },
|
|
e: { k: '3a', ref: doc(db, '3', 'a') }
|
|
};
|
|
return withTestCollection(persistence, testDocs, coll => {
|
|
const sortedQuery = query(coll, orderBy('ref'));
|
|
return getDocs(
|
|
query(
|
|
sortedQuery,
|
|
startAfter(doc(db, '1', 'a')),
|
|
endAt(doc(db, '2', 'b'))
|
|
)
|
|
).then(docs => {
|
|
expect(toDataArray(docs).map(v => v['k'])).to.deep.equal([
|
|
'1b',
|
|
'2a',
|
|
'2b'
|
|
]);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be used in descending queries', () => {
|
|
const testDocs = {
|
|
a: { k: 'a', sort: 1 },
|
|
b: { k: 'b', sort: 2 },
|
|
c: { k: 'c', sort: 2 },
|
|
d: { k: 'd', sort: 3 },
|
|
e: { k: 'e', sort: 0 },
|
|
f: { k: 'f', nosort: 1 } // should not show up
|
|
};
|
|
return withTestCollection(persistence, testDocs, coll => {
|
|
const sortedQuery = query(
|
|
coll,
|
|
orderBy('sort', 'desc'),
|
|
// default indexes reverse the key ordering for descending sorts
|
|
orderBy(documentId(), 'desc')
|
|
);
|
|
return getDocs(query(sortedQuery, startAt(2)))
|
|
.then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([
|
|
{ k: 'c', sort: 2 },
|
|
{ k: 'b', sort: 2 },
|
|
{ k: 'a', sort: 1 },
|
|
{ k: 'e', sort: 0 }
|
|
]);
|
|
return getDocs(query(sortedQuery, endBefore(2)));
|
|
})
|
|
.then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([{ k: 'd', sort: 3 }]);
|
|
});
|
|
});
|
|
});
|
|
|
|
// Currently, timestamps are truncated to microseconds on the backend, so
|
|
// don't create timestamps with more precision than that.
|
|
const makeTimestamp = (seconds: number, micros: number): TimestampInstance =>
|
|
new Timestamp(seconds, micros * 1000);
|
|
|
|
it('can accept Timestamps as bounds', () => {
|
|
const testDocs = {
|
|
a: { timestamp: makeTimestamp(100, 2) },
|
|
b: { timestamp: makeTimestamp(100, 5) },
|
|
c: { timestamp: makeTimestamp(100, 3) },
|
|
d: { timestamp: makeTimestamp(100, 1) },
|
|
// Number of microseconds deliberately repeated.
|
|
e: { timestamp: makeTimestamp(100, 5) },
|
|
f: { timestamp: makeTimestamp(100, 4) }
|
|
};
|
|
return withTestCollection(persistence, testDocs, coll => {
|
|
return getDocs(
|
|
query(
|
|
coll,
|
|
orderBy('timestamp'),
|
|
startAfter(makeTimestamp(100, 2)),
|
|
endAt(makeTimestamp(100, 5))
|
|
)
|
|
).then(docs => {
|
|
expect(toIds(docs)).to.deep.equal(['c', 'f', 'b', 'e']);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can accept Timestamps in where clause', () => {
|
|
const testDocs = {
|
|
a: { timestamp: makeTimestamp(100, 7) },
|
|
b: { timestamp: makeTimestamp(100, 4) },
|
|
c: { timestamp: makeTimestamp(100, 8) },
|
|
d: { timestamp: makeTimestamp(100, 5) },
|
|
e: { timestamp: makeTimestamp(100, 6) }
|
|
};
|
|
return withTestCollection(persistence, testDocs, coll => {
|
|
return getDocs(
|
|
query(
|
|
coll,
|
|
where('timestamp', '>=', makeTimestamp(100, 5)),
|
|
where('timestamp', '<', makeTimestamp(100, 8))
|
|
)
|
|
).then(docs => {
|
|
expect(toIds(docs)).to.deep.equal(['d', 'e', 'a']);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('truncate Timestamps', () => {
|
|
const nanos = new Timestamp(0, 123456789);
|
|
const micros = new Timestamp(0, 123456000);
|
|
const millis = new Timestamp(0, 123000000);
|
|
|
|
const testDocs = {
|
|
a: { timestamp: nanos }
|
|
};
|
|
return withTestCollection(persistence, testDocs, coll => {
|
|
return getDocs(query(coll, where('timestamp', '==', nanos)))
|
|
.then(docs => {
|
|
expect(toIds(docs)).to.deep.equal(['a']);
|
|
return getDocs(query(coll, where('timestamp', '==', micros)));
|
|
})
|
|
.then(docs => {
|
|
// Because Timestamp should have been truncated to microseconds, the
|
|
// microsecond timestamp should be considered equal to the
|
|
// nanosecond one.
|
|
expect(toIds(docs)).to.deep.equal(['a']);
|
|
return getDocs(query(coll, where('timestamp', '==', millis)));
|
|
})
|
|
.then(docs => {
|
|
// The truncation is just to the microseconds, however, so the
|
|
// millisecond timestamp should be treated as different and thus the
|
|
// query should return no results.
|
|
expect(toIds(docs)).to.be.empty;
|
|
});
|
|
});
|
|
});
|
|
});
|