UNPKG

firestore-vitest

Version:

Vitest helper for mocking Google Cloud Firestore

339 lines (304 loc) 13.4 kB
import { FakeFirestore } from '../index.js'; import { mockCollection, mockDoc } from '../mocks/firestore.js'; describe('Queries', () => { beforeEach(() => { vi.resetModules(); vi.clearAllMocks(); }); const db = (simulateQueryFilters = false) => new FakeFirestore( { characters: [ { id: 'homer', name: 'Homer', occupation: 'technician', address: { street: '742 Evergreen Terrace' }, // May 12, 1956. Conveniently, a negative number birthdate: { seconds: -430444800, nanoseconds: 0, }, // Test a pre-constructed Timestamp timestamp: new FakeFirestore.Timestamp(123, 456), }, { id: 'krusty', name: 'Krusty', occupation: 'clown' }, { id: 'bob', name: 'Bob', occupation: 'insurance agent', _collections: { family: [ { id: 'violet', name: 'Violet', relation: 'daughter' }, { id: 'dash', name: 'Dash', relation: 'son' }, { id: 'jackjack', name: 'Jackjack', relation: 'son' }, { id: 'helen', name: 'Helen', relation: 'wife' }, ], }, }, ], checkEmpty: [ { id: 'emptyDocument', _collections: { validChildren: { family: [ { id: '1', name: 'One' }, { id: '2', name: 'Two' }, { id: '3', name: 'Three' }, ], }, }, }, ], }, { simulateQueryFilters }, ); describe('Single records versus queries', () => { test('it can fetch a single record', async () => { expect.assertions(6); const record = await db().collection('characters').doc('krusty').get(); expect(mockCollection).toHaveBeenCalledWith('characters'); expect(mockDoc).toHaveBeenCalledWith('krusty'); expect(record.exists).toBe(true); expect(record.id).toBe('krusty'); const data = record.data(); expect(data).toHaveProperty('name', 'Krusty'); expect(data).toHaveProperty('occupation', 'clown'); }); test('it flags records do not exist', async () => { expect.assertions(4); const record = await db().collection('animals').doc('monkey').get(); expect(mockCollection).toHaveBeenCalledWith('animals'); expect(mockDoc).toHaveBeenCalledWith('monkey'); expect(record.id).toBe('monkey'); expect(record.exists).toBe(false); }); test('it can fetch a single record with a promise', () => db() .collection('characters') .doc('homer') .get() .then(record => { expect(record.exists).toBe(true); expect(record.id).toBe('homer'); expect(mockCollection).toHaveBeenCalledWith('characters'); const data = record.data(); expect(record).toHaveProperty('exists', true); expect(data).toBeDefined(); expect(data).toHaveProperty('name', 'Homer'); expect(data).toHaveProperty('occupation', 'technician'); expect(record.get('name')).toBe('Homer'); expect(record.get('address.street')).toBe('742 Evergreen Terrace'); expect(record.get('address.street.doesntExist')).toBeNull(); })); test('it can fetch a single record with a promise without a specified collection', () => db() .doc('characters/homer') .get() .then(record => { expect(record.exists).toBe(true); expect(record.id).toBe('homer'); expect(mockCollection).not.toHaveBeenCalled(); const data = record.data(); expect(record).toHaveProperty('exists', true); expect(data).toBeDefined(); expect(data).toHaveProperty('name', 'Homer'); expect(data).toHaveProperty('occupation', 'technician'); })); test('it can fetch multiple records and returns documents', async () => { const records = await db().collection('characters').where('name', '==', 'Homer').get(); expect(records.empty).toBe(false); expect(records).toHaveProperty('docs', expect.any(Array)); const doc = records.docs[0]; expect(doc).toHaveProperty('id', 'homer'); expect(doc).toHaveProperty('exists', true); const data = doc.data(); expect(data).toBeDefined(); expect(data).toHaveProperty('name', 'Homer'); }); test('it throws an error if the collection path ends at a document', () => { expect(() => db().collection('')).toThrow(Error); expect(db().collection('foo')).toBeInstanceOf(FakeFirestore.CollectionReference); expect(() => db().collection('foo/bar')).toThrow(Error); expect(db().collection('foo/bar/baz')).toBeInstanceOf(FakeFirestore.CollectionReference); }); test('it throws an error if the document path ends at a collection', () => { expect(() => db().doc('')).toThrow(Error); expect(() => db().doc('characters')).toThrow(Error); expect(db().doc('characters/bob')).toBeInstanceOf(FakeFirestore.DocumentReference); expect(() => db().doc('characters/bob/family')).toThrow(Error); }); test('it can fetch nonexistent documents from a root collection', async () => { const nope = await db().doc('characters/joe').get(); expect(nope).toHaveProperty('exists', false); expect(nope).toHaveProperty('id', 'joe'); expect(nope).toHaveProperty('ref'); expect(nope.ref).toHaveProperty('path', 'characters/joe'); }); test('it can fetch nonexistent documents from extant subcollections', async () => { const nope = await db().doc('characters/bob/family/thing3').get(); expect(nope).toHaveProperty('exists', false); expect(nope).toHaveProperty('id', 'thing3'); expect(nope).toHaveProperty('ref'); expect(nope.ref).toHaveProperty('path', 'characters/bob/family/thing3'); }); test('it can fetch nonexistent documents from nonexistent subcollections', async () => { const nope = await db().doc('characters/sam/family/phil').get(); expect(nope).toHaveProperty('exists', false); expect(nope).toHaveProperty('id', 'phil'); expect(nope).toHaveProperty('ref'); expect(nope.ref).toHaveProperty('path', 'characters/sam/family/phil'); }); test('it can fetch nonexistent documents from nonexistent root collections', async () => { const nope = await db().doc('foo/bar/baz/bin').get(); expect(nope).toHaveProperty('exists', false); expect(nope).toHaveProperty('id', 'bin'); expect(nope).toHaveProperty('ref'); expect(nope.ref).toHaveProperty('path', 'foo/bar/baz/bin'); }); test('it flags when a collection is empty', async () => { expect.assertions(1); const records = await db().collection('animals').where('type', '==', 'mammal').get(); expect(records).toHaveProperty('empty', true); }); test.each` simulateQueryFilters | expectedSize ${true} | ${1} ${false} | ${3} `('it can fetch multiple records as a promise', ({ simulateQueryFilters, expectedSize }) => db(simulateQueryFilters) .collection('characters') .where('name', '==', 'Homer') .get() .then(records => { expect(records).toHaveProperty('empty', false); expect(records).toHaveProperty('docs', expect.any(Array)); expect(records).toHaveProperty('size', expectedSize); expect(records.docs[0]).toHaveProperty('id', 'homer'); expect(records.docs[0]).toHaveProperty('exists', true); expect(records.docs[0].data()).toHaveProperty('name', 'Homer'); }), ); test('it can return all root records', async () => { expect.assertions(4); const firstRecord = db().collection('characters').doc('homer'); const secondRecord = db().collection('characters').doc('krusty'); const records = await db().getAll(firstRecord, secondRecord); expect(records.length).toBe(2); expect(records[0]).toHaveProperty('id', 'homer'); expect(records[0]).toHaveProperty('exists', true); expect(records[0].data()).toHaveProperty('name', 'Homer'); }); test('it does not fetch subcollections unless we tell it to', async () => { expect.assertions(4); const record = await db().collection('characters').doc('bob').get(); expect(record.exists).toBe(true); expect(record.id).toBe('bob'); expect(record.data()).toHaveProperty('name', 'Bob'); expect(record.data()).not.toHaveProperty('_collections'); }); test('it can fetch records from subcollections', async () => { expect.assertions(8); const family = db().collection('characters').doc('bob').collection('family'); expect(family.path).toBe('characters/bob/family'); const allFamilyMembers = await family.get(); expect(allFamilyMembers.docs.length).toBe(4); expect(allFamilyMembers.forEach).toBeTruthy(); const ref = family.doc('violet'); expect(ref).toHaveProperty('path', 'characters/bob/family/violet'); const record = await ref.get(); expect(record).toHaveProperty('exists', true); expect(record).toHaveProperty('id', 'violet'); expect(record).toHaveProperty('data'); expect(record.data()).toHaveProperty('name', 'Violet'); }); test.each` simulateQueryFilters | expectedSize ${true} | ${2} ${false} | ${4} `( 'it can fetch records from subcollections with query parameters', async ({ simulateQueryFilters, expectedSize }) => { const family = db(simulateQueryFilters) .collection('characters') .doc('bob') .collection('family') .where('relation', '==', 'son'); // should return only sons expect(family).toHaveProperty('path', 'characters/bob/family'); const docs = await family.get(); expect(docs).toHaveProperty('size', expectedSize); }, ); }); describe('Multiple records versus queries', () => { test('it fetches all records from a root collection', async () => { expect.assertions(4); const characters = await db().collection('characters').get(); expect(characters).toHaveProperty('empty', false); expect(characters).toHaveProperty('size', 3); expect(Array.isArray(characters.docs)).toBe(true); expect(characters.forEach).toBeTruthy(); }); test('it fetches no records from nonexistent collection', async () => { expect.assertions(4); const nope = await db().collection('foo').get(); expect(nope).toHaveProperty('empty', true); expect(nope).toHaveProperty('size', 0); expect(Array.isArray(nope.docs)).toBe(true); expect(nope.forEach).toBeTruthy(); }); test('it fetches all records from subcollection', async () => { expect.assertions(4); const familyRef = db().collection('characters').doc('bob').collection('family'); const family = await familyRef.get(); expect(family).toHaveProperty('empty', false); expect(family).toHaveProperty('size', 4); expect(Array.isArray(family.docs)).toBe(true); expect(family.forEach).toBeTruthy(); }); test('it fetches no records from nonexistent subcollection', async () => { expect.assertions(4); const nope = await db().collection('characters').doc('bob').collection('not-here').get(); expect(nope).toHaveProperty('empty', true); expect(nope).toHaveProperty('size', 0); expect(Array.isArray(nope.docs)).toBe(true); expect(nope.forEach).toBeTruthy(); }); test('it fetches no records from nonexistent root collection', async () => { expect.assertions(4); const nope = await db().collection('foo').doc('bar').collection('baz').get(); expect(nope).toHaveProperty('empty', true); expect(nope).toHaveProperty('size', 0); expect(Array.isArray(nope.docs)).toBe(true); expect(nope.forEach).toBeTruthy(); }); }); test('it returns all results from listDocuments', async () => { const [emptyDoc] = await db().collection('checkEmpty').listDocuments(); expect(emptyDoc).toBeDefined(); const data = await emptyDoc.get(); expect(data.exists).toBeTruthy(); // Contains no data expect(Object.keys(data.data())).toHaveLength(0); }); test('New documents with random ID', async () => { expect.assertions(2); // See https://firebase.google.com/docs/reference/js/firestore_#doc // "If no path is specified, an automatically-generated unique ID will be used for the returned DocumentReference." const col = db().collection('foo'); const newDoc = col.doc(); const otherIds = col._records().map(doc => doc.id); expect(otherIds).not.toContainEqual(newDoc.id); expect(newDoc).toHaveProperty('path', `foo/${newDoc.id}`); }); test('it properly converts timestamps', () => db() .doc('characters/homer') .get() .then(record => { expect(record.id).toBe('homer'); const data = record.data(); expect(typeof data.birthdate.toDate).toBe('function'); })); });