mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
453 lines
13 KiB
TypeScript
453 lines
13 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 { expect } from 'chai';
|
|
|
|
import * as firebaseExport from './util/firebase_export';
|
|
import {
|
|
apiDescribe,
|
|
toDataArray,
|
|
withTestCollection,
|
|
withTestCollectionSettings,
|
|
withTestDoc,
|
|
withTestDocAndSettings
|
|
} from './util/helpers';
|
|
import { DEFAULT_SETTINGS } from './util/settings';
|
|
|
|
const FieldPath = firebaseExport.FieldPath;
|
|
const Timestamp = firebaseExport.Timestamp;
|
|
|
|
// Allow custom types for testing.
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
type AnyTestData = any;
|
|
|
|
apiDescribe('Nested Fields', (persistence: boolean) => {
|
|
const testData = (n?: number): AnyTestData => {
|
|
n = n || 1;
|
|
return {
|
|
name: 'room ' + n,
|
|
metadata: {
|
|
createdAt: n,
|
|
deep: {
|
|
field: 'deep-field-' + n
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
it('can be written with set()', () => {
|
|
return withTestDoc(persistence, doc => {
|
|
return doc
|
|
.set(testData())
|
|
.then(() => doc.get())
|
|
.then(docSnap => {
|
|
expect(docSnap.data()).to.deep.equal(testData());
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be read directly with .get(<string>)', () => {
|
|
return withTestDoc(persistence, doc => {
|
|
const obj = testData();
|
|
return doc
|
|
.set(obj)
|
|
.then(() => doc.get())
|
|
.then(docSnap => {
|
|
expect(docSnap.data()).to.deep.equal(obj);
|
|
expect(docSnap.get('name')).to.deep.equal(obj.name);
|
|
expect(docSnap.get('metadata')).to.deep.equal(obj.metadata);
|
|
expect(docSnap.get('metadata.deep.field')).to.deep.equal(
|
|
obj.metadata.deep.field
|
|
);
|
|
expect(docSnap.get('metadata.nofield')).to.be.undefined;
|
|
expect(docSnap.get('nometadata.nofield')).to.be.undefined;
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be read directly with .get(<FieldPath>)', () => {
|
|
return withTestDoc(persistence, doc => {
|
|
const obj = testData();
|
|
return doc
|
|
.set(obj)
|
|
.then(() => doc.get())
|
|
.then(docSnap => {
|
|
expect(docSnap.data()).to.deep.equal(obj);
|
|
expect(docSnap.get(new FieldPath('name'))).to.deep.equal(obj.name);
|
|
expect(docSnap.get(new FieldPath('metadata'))).to.deep.equal(
|
|
obj.metadata
|
|
);
|
|
expect(
|
|
docSnap.get(new FieldPath('metadata', 'deep', 'field'))
|
|
).to.deep.equal(obj.metadata.deep.field);
|
|
expect(docSnap.get(new FieldPath('metadata', 'nofield'))).to.be
|
|
.undefined;
|
|
expect(docSnap.get(new FieldPath('nometadata', 'nofield'))).to.be
|
|
.undefined;
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be updated with update(<string>)', () => {
|
|
return withTestDoc(persistence, doc => {
|
|
return doc
|
|
.set(testData())
|
|
.then(() => {
|
|
return doc.update({
|
|
'metadata.deep.field': 100,
|
|
'metadata.added': 200
|
|
});
|
|
})
|
|
.then(() => doc.get())
|
|
.then(docSnap => {
|
|
expect(docSnap.data()).to.deep.equal({
|
|
name: 'room 1',
|
|
metadata: {
|
|
createdAt: 1,
|
|
deep: {
|
|
field: 100
|
|
},
|
|
added: 200
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be updated with update(<FieldPath>)', () => {
|
|
return withTestDoc(persistence, doc => {
|
|
return doc
|
|
.set(testData())
|
|
.then(() => {
|
|
return doc.update(
|
|
new FieldPath('metadata', 'deep', 'field'),
|
|
100,
|
|
new FieldPath('metadata', 'added'),
|
|
200
|
|
);
|
|
})
|
|
.then(() => doc.get())
|
|
.then(docSnap => {
|
|
expect(docSnap.data()).to.deep.equal({
|
|
name: 'room 1',
|
|
metadata: {
|
|
createdAt: 1,
|
|
deep: {
|
|
field: 100
|
|
},
|
|
added: 200
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be used with query.where(<string>).', () => {
|
|
const testDocs = {
|
|
'1': testData(300),
|
|
'2': testData(100),
|
|
'3': testData(200)
|
|
};
|
|
return withTestCollection(persistence, testDocs, coll => {
|
|
return coll
|
|
.where('metadata.createdAt', '>=', 200)
|
|
.get()
|
|
.then(results => {
|
|
// inequality adds implicit sort on field
|
|
expect(toDataArray(results)).to.deep.equal([
|
|
testData(200),
|
|
testData(300)
|
|
]);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be used with query.where(<FieldPath>).', () => {
|
|
const testDocs = {
|
|
'1': testData(300),
|
|
'2': testData(100),
|
|
'3': testData(200)
|
|
};
|
|
return withTestCollection(persistence, testDocs, coll => {
|
|
return coll
|
|
.where(new FieldPath('metadata', 'createdAt'), '>=', 200)
|
|
.get()
|
|
.then(results => {
|
|
// inequality adds implicit sort on field
|
|
expect(toDataArray(results)).to.deep.equal([
|
|
testData(200),
|
|
testData(300)
|
|
]);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be used with query.orderBy(<string>).', () => {
|
|
const testDocs = {
|
|
'1': testData(300),
|
|
'2': testData(100),
|
|
'3': testData(200)
|
|
};
|
|
return withTestCollection(persistence, testDocs, coll => {
|
|
return coll
|
|
.orderBy('metadata.createdAt')
|
|
.get()
|
|
.then(results => {
|
|
expect(toDataArray(results)).to.deep.equal([
|
|
testData(100),
|
|
testData(200),
|
|
testData(300)
|
|
]);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be used with query.orderBy(<FieldPath>).', () => {
|
|
const testDocs = {
|
|
'1': testData(300),
|
|
'2': testData(100),
|
|
'3': testData(200)
|
|
};
|
|
return withTestCollection(persistence, testDocs, coll => {
|
|
return coll
|
|
.orderBy(new FieldPath('metadata', 'createdAt'))
|
|
.get()
|
|
.then(results => {
|
|
expect(toDataArray(results)).to.deep.equal([
|
|
testData(100),
|
|
testData(200),
|
|
testData(300)
|
|
]);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// NOTE(mikelehen): I originally combined these tests with the above ones, but
|
|
// Datastore currently prohibits having nested fields and fields with dots in
|
|
// the same entity, so I'm separating them.
|
|
apiDescribe('Fields with special characters', (persistence: boolean) => {
|
|
const testData = (n?: number): AnyTestData => {
|
|
n = n || 1;
|
|
return {
|
|
field: 'field ' + n,
|
|
'field.dot': n,
|
|
'field\\slash': n
|
|
};
|
|
};
|
|
|
|
it('can be written with set()', () => {
|
|
return withTestDoc(persistence, doc => {
|
|
return doc
|
|
.set(testData())
|
|
.then(() => doc.get())
|
|
.then(docSnap => {
|
|
expect(docSnap.data()).to.deep.equal(testData());
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be read directly with .data(<field>)', () => {
|
|
return withTestDoc(persistence, doc => {
|
|
const obj = testData();
|
|
return doc
|
|
.set(obj)
|
|
.then(() => doc.get())
|
|
.then(docSnap => {
|
|
expect(docSnap.data()).to.deep.equal(obj);
|
|
expect(docSnap.get(new FieldPath('field.dot'))).to.deep.equal(
|
|
obj['field.dot']
|
|
);
|
|
expect(docSnap.get('field\\slash')).to.deep.equal(
|
|
obj['field\\slash']
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be updated with update()', () => {
|
|
return withTestDoc(persistence, doc => {
|
|
return doc
|
|
.set(testData())
|
|
.then(() => {
|
|
return doc.update(
|
|
new FieldPath('field.dot'),
|
|
100,
|
|
'field\\slash',
|
|
200
|
|
);
|
|
})
|
|
.then(() => doc.get())
|
|
.then(docSnap => {
|
|
expect(docSnap.data()).to.deep.equal({
|
|
field: 'field 1',
|
|
'field.dot': 100,
|
|
'field\\slash': 200
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be used in query filters.', () => {
|
|
const testDocs = {
|
|
'1': testData(300),
|
|
'2': testData(100),
|
|
'3': testData(200)
|
|
};
|
|
return withTestCollection(persistence, testDocs, coll => {
|
|
// inequality adds implicit sort on field
|
|
const expected = [testData(200), testData(300)];
|
|
return coll
|
|
.where(new FieldPath('field.dot'), '>=', 200)
|
|
.get()
|
|
.then(results => {
|
|
expect(toDataArray(results)).to.deep.equal(expected);
|
|
})
|
|
.then(() => coll.where('field\\slash', '>=', 200).get())
|
|
.then(results => {
|
|
expect(toDataArray(results)).to.deep.equal(expected);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('can be used in a query orderBy.', () => {
|
|
const testDocs = {
|
|
'1': testData(300),
|
|
'2': testData(100),
|
|
'3': testData(200)
|
|
};
|
|
return withTestCollection(persistence, testDocs, coll => {
|
|
const expected = [testData(100), testData(200), testData(300)];
|
|
return coll
|
|
.orderBy(new FieldPath('field.dot'))
|
|
.get()
|
|
.then(results => {
|
|
expect(toDataArray(results)).to.deep.equal(expected);
|
|
})
|
|
.then(() => coll.orderBy('field\\slash').get())
|
|
.then(results => {
|
|
expect(toDataArray(results)).to.deep.equal(expected);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
apiDescribe('Timestamp Fields in snapshots', (persistence: boolean) => {
|
|
// Figure out how to pass in the Timestamp type
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const testDataWithTimestamps = (ts: any): AnyTestData => {
|
|
return { timestamp: ts, nested: { timestamp2: ts } };
|
|
};
|
|
|
|
it('are returned as Timestamps', () => {
|
|
const timestamp = new Timestamp(100, 123456000);
|
|
// Timestamps are currently truncated to microseconds after being written to
|
|
// the database, so a truncated version of the timestamp is needed for
|
|
// comparisons.
|
|
const truncatedTimestamp = new Timestamp(
|
|
timestamp.seconds,
|
|
Math.floor(timestamp.nanoseconds / 1000) * 1000
|
|
);
|
|
|
|
return withTestDoc(persistence, doc => {
|
|
return doc
|
|
.set(testDataWithTimestamps(timestamp))
|
|
.then(() => doc.get())
|
|
.then(docSnap => {
|
|
expect(docSnap.get('timestamp'))
|
|
.to.be.an.instanceof(Timestamp)
|
|
.that.deep.equals(truncatedTimestamp);
|
|
expect(docSnap.data()!['timestamp'])
|
|
.to.be.an.instanceof(Timestamp)
|
|
.that.deep.equals(truncatedTimestamp);
|
|
|
|
expect(docSnap.get('nested.timestamp2'))
|
|
.to.be.an.instanceof(Timestamp)
|
|
.that.deep.equals(truncatedTimestamp);
|
|
expect(docSnap.data()!['nested']['timestamp2'])
|
|
.to.be.an.instanceof(Timestamp)
|
|
.that.deep.equals(truncatedTimestamp);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
apiDescribe('`undefined` properties', (persistence: boolean) => {
|
|
const settings = { ...DEFAULT_SETTINGS, ignoreUndefinedProperties: true };
|
|
|
|
it('are ignored in set()', () => {
|
|
return withTestDocAndSettings(persistence, settings, async doc => {
|
|
await doc.set({ foo: 'foo', 'bar': undefined });
|
|
const docSnap = await doc.get();
|
|
expect(docSnap.data()).to.deep.equal({ foo: 'foo' });
|
|
});
|
|
});
|
|
|
|
it('are ignored in set({ merge: true })', () => {
|
|
return withTestDocAndSettings(persistence, settings, async doc => {
|
|
await doc.set({ foo: 'foo', bar: 'unchanged' });
|
|
await doc.set({ foo: 'foo', bar: undefined }, { merge: true });
|
|
const docSnap = await doc.get();
|
|
expect(docSnap.data()).to.deep.equal({ foo: 'foo', bar: 'unchanged' });
|
|
});
|
|
});
|
|
|
|
it('are ignored in update()', () => {
|
|
return withTestDocAndSettings(persistence, settings, async doc => {
|
|
await doc.set({});
|
|
await doc.update({ a: { foo: 'foo', 'bar': undefined } });
|
|
await doc.update('b', { foo: 'foo', 'bar': undefined });
|
|
const docSnap = await doc.get();
|
|
expect(docSnap.data()).to.deep.equal({
|
|
a: { foo: 'foo' },
|
|
b: { foo: 'foo' }
|
|
});
|
|
});
|
|
});
|
|
|
|
it('are ignored in Query.where()', () => {
|
|
return withTestCollectionSettings(
|
|
persistence,
|
|
settings,
|
|
{ 'doc1': { nested: { foo: 'foo' } } },
|
|
async coll => {
|
|
const query = coll.where('nested', '==', {
|
|
foo: 'foo',
|
|
'bar': undefined
|
|
});
|
|
const querySnap = await query.get();
|
|
expect(querySnap.size).to.equal(1);
|
|
}
|
|
);
|
|
});
|
|
|
|
it('are ignored in Query.startAt()', () => {
|
|
return withTestCollectionSettings(
|
|
persistence,
|
|
settings,
|
|
{ 'doc1': { nested: { foo: 'foo' } } },
|
|
async coll => {
|
|
const query = coll
|
|
.orderBy('nested')
|
|
.startAt({ foo: 'foo', 'bar': undefined });
|
|
const querySnap = await query.get();
|
|
expect(querySnap.size).to.equal(1);
|
|
}
|
|
);
|
|
});
|
|
});
|