mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
313 lines
9.7 KiB
TypeScript
313 lines
9.7 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2022 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 * as firebaseExport from './util/firebase_export';
|
|
import {
|
|
apiDescribe,
|
|
toDataArray,
|
|
toIds,
|
|
withTestCollection,
|
|
withTestDb,
|
|
withTestDbs
|
|
} from './util/helpers';
|
|
|
|
const Timestamp = firebaseExport.Timestamp;
|
|
const FieldPath = firebaseExport.FieldPath;
|
|
|
|
apiDescribe('Cursors', (persistence: boolean) => {
|
|
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 coll
|
|
.limit(2)
|
|
.get()
|
|
.then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([{ v: 'a' }, { v: 'b' }]);
|
|
const lastDoc = docs.docs[docs.docs.length - 1];
|
|
return coll.limit(3).startAfter(lastDoc).get();
|
|
})
|
|
.then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([
|
|
{ v: 'c' },
|
|
{ v: 'd' },
|
|
{ v: 'e' }
|
|
]);
|
|
const lastDoc = docs.docs[docs.docs.length - 1];
|
|
return coll.limit(1).startAfter(lastDoc).get();
|
|
})
|
|
.then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([{ v: 'f' }]);
|
|
const lastDoc = docs.docs[docs.docs.length - 1];
|
|
return coll.limit(3).startAfter(lastDoc).get();
|
|
})
|
|
.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 query = coll.orderBy('sort');
|
|
return coll
|
|
.doc('c')
|
|
.get()
|
|
.then(doc => {
|
|
expect(doc.data()).to.deep.equal({ k: 'c', sort: 2 });
|
|
return query
|
|
.startAt(doc)
|
|
.get()
|
|
.then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([
|
|
{ k: 'c', sort: 2 },
|
|
{ k: 'd', sort: 2 }
|
|
]);
|
|
return query.endBefore(doc).get();
|
|
});
|
|
})
|
|
.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 query = coll.orderBy('sort');
|
|
return query
|
|
.startAt(2)
|
|
.get()
|
|
.then(docs => {
|
|
expect(toDataArray(docs)).to.deep.equal([
|
|
{ k: 'b', sort: 2 },
|
|
{ k: 'c', sort: 2 },
|
|
{ k: 'd', sort: 2 }
|
|
]);
|
|
return query.endBefore(2).get();
|
|
})
|
|
.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 = writer
|
|
.collection('parent-collection')
|
|
.doc()
|
|
.collection('sub-collection');
|
|
const readerCollection = reader.collection(writerCollection.path);
|
|
const sets: Array<Promise<void>> = [];
|
|
Object.keys(testDocs).forEach((key: string) => {
|
|
sets.push(writerCollection.doc(key).set(testDocs[key]));
|
|
});
|
|
|
|
return Promise.all(sets)
|
|
.then(() => {
|
|
return readerCollection
|
|
.orderBy(FieldPath.documentId())
|
|
.startAt('b')
|
|
.endBefore('d')
|
|
.get();
|
|
})
|
|
.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: db.collection('1').doc('a') },
|
|
b: { k: '1b', ref: db.collection('1').doc('b') },
|
|
c: { k: '2a', ref: db.collection('2').doc('a') },
|
|
d: { k: '2b', ref: db.collection('2').doc('b') },
|
|
e: { k: '3a', ref: db.collection('3').doc('a') }
|
|
};
|
|
return withTestCollection(persistence, testDocs, coll => {
|
|
const query = coll.orderBy('ref');
|
|
return query
|
|
.startAfter(db.collection('1').doc('a'))
|
|
.endAt(db.collection('2').doc('b'))
|
|
.get()
|
|
.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 query = coll
|
|
.orderBy('sort', 'desc')
|
|
// default indexes reverse the key ordering for descending sorts
|
|
.orderBy(FieldPath.documentId(), 'desc');
|
|
return query
|
|
.startAt(2)
|
|
.get()
|
|
.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 query.endBefore(2).get();
|
|
})
|
|
.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 coll
|
|
.orderBy('timestamp')
|
|
.startAfter(makeTimestamp(100, 2))
|
|
.endAt(makeTimestamp(100, 5))
|
|
.get()
|
|
.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 coll
|
|
.where('timestamp', '>=', makeTimestamp(100, 5))
|
|
.where('timestamp', '<', makeTimestamp(100, 8))
|
|
.get()
|
|
.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 coll
|
|
.where('timestamp', '==', nanos)
|
|
.get()
|
|
.then(docs => {
|
|
expect(toIds(docs)).to.deep.equal(['a']);
|
|
return coll.where('timestamp', '==', micros).get();
|
|
})
|
|
.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 coll.where('timestamp', '==', millis).get();
|
|
})
|
|
.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;
|
|
});
|
|
});
|
|
});
|
|
});
|