UNPKG

voluptasmollitia

Version:
349 lines (300 loc) 11.6 kB
/** * @license * Copyright 2018 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. */ /* eslint-disable @typescript-eslint/no-floating-promises */ import { expect } from 'chai'; // app is used as namespaces to access types // eslint-disable-next-line @typescript-eslint/no-unused-vars import firebase from 'firebase/app'; import 'firebase/firestore'; import { collection, collectionChanges, sortedChanges, auditTrail, docData, collectionData } from '../firestore'; import { map, take, skip } from 'rxjs/operators'; // eslint-disable-next-line @typescript-eslint/no-require-imports export const TEST_PROJECT = require('../../../config/project.json'); const createId = (): string => Math.random().toString(36).substring(5); /** * Create a collection with a random name. This helps sandbox offline tests and * makes sure tests don't interfere with each other as they run. */ const createRandomCol = ( firestore: firebase.firestore.Firestore ): firebase.firestore.CollectionReference => firestore.collection(createId()); /** * Unwrap a snapshot but add the type property to the data object. */ const unwrapChange = map((changes: firebase.firestore.DocumentChange[]) => { return changes.map(c => ({ type: c.type, ...c.doc.data() })); }); /** * Create an environment for the tests to run in. The information is returned * from the function for use within the test. */ const seedTest = (firestore: firebase.firestore.Firestore): any => { const colRef = createRandomCol(firestore); const davidDoc = colRef.doc('david'); davidDoc.set({ name: 'David' }); const shannonDoc = colRef.doc('shannon'); shannonDoc.set({ name: 'Shannon' }); const expectedNames = ['David', 'Shannon']; const expectedEvents = [ { name: 'David', type: 'added' }, { name: 'Shannon', type: 'added' } ]; return { colRef, davidDoc, shannonDoc, expectedNames, expectedEvents }; }; describe('RxFire Firestore', () => { let app: firebase.app.App; let firestore: firebase.firestore.Firestore; /** * Each test runs inside it's own app instance and the app * is deleted after the test runs. * * Firestore tests run "offline" to reduce "flakeyness". * * Each test is responsible for seeding and removing data. Helper * functions are useful if the process becomes brittle or tedious. * Note that removing is less necessary since the tests are run * offline. */ beforeEach(() => { app = firebase.initializeApp({ projectId: TEST_PROJECT.projectId }); firestore = app.firestore(); firestore.disableNetwork(); }); afterEach((done: MochaDone) => { app.delete().then(() => done()); }); describe('collection', () => { /** * This is a simple test to see if the collection() method * correctly emits snapshots. * * The test seeds two "people" into the collection. RxFire * creats an observable with the `collection()` method and * asserts that the two "people" are in the array. */ it('should emit snapshots', (done: MochaDone) => { const { colRef, expectedNames } = seedTest(firestore); collection(colRef) .pipe(map(docs => docs.map(doc => doc.data().name))) .subscribe(names => { expect(names).to.eql(expectedNames); done(); }); }); }); describe('collectionChanges', () => { /** * The `stateChanges()` method emits a stream of events as they * occur rather than in sorted order. * * This test adds a "person" and then modifies it. This should * result in an array item of "added" and then "modified". */ it('should emit events as they occur', (done: MochaDone) => { const { colRef, davidDoc } = seedTest(firestore); davidDoc.set({ name: 'David' }); const firstChange = collectionChanges(colRef).pipe(take(1)); const secondChange = collectionChanges(colRef).pipe(skip(1)); firstChange.subscribe(change => { expect(change[0].type).to.eq('added'); davidDoc.update({ name: 'David!' }); }); secondChange.subscribe(change => { expect(change[0].type).to.eq('modified'); done(); }); }); }); describe('sortedChanges', () => { /** * The `sortedChanges()` method reduces the stream of `collectionChanges()` to * a sorted array. This test seeds two "people" and checks to make sure * the 'added' change type exists. Afterwards, one "person" is modified. * The test then checks that the person is modified and in the proper sorted * order. */ it('should emit an array of sorted snapshots', (done: MochaDone) => { const { colRef, davidDoc } = seedTest(firestore); const addedChanges = sortedChanges(colRef, ['added']).pipe(unwrapChange); const modifiedChanges = sortedChanges(colRef).pipe( unwrapChange, skip(1), take(1) ); let previousData: Array<{}>; addedChanges.subscribe(data => { const expectedNames = [ { name: 'David', type: 'added' }, { name: 'Shannon', type: 'added' } ]; expect(data).to.eql(expectedNames); previousData = data; davidDoc.update({ name: 'David!' }); }); modifiedChanges.subscribe(data => { const expectedNames = [ { name: 'David!', type: 'modified' }, { name: 'Shannon', type: 'added' } ]; expect(data).to.eql(expectedNames); expect(data === previousData).to.eql(false); done(); }); }); /** * The events parameter in `sortedChanges()` filters out events by change * type. This test seeds two "people" and creates two observables to test * the filtering. The first observable filters to 'added' and the second * filters to 'modified'. */ it('should filter by event type', (done: MochaDone) => { const { colRef, davidDoc, expectedEvents } = seedTest(firestore); const addedChanges = sortedChanges(colRef, ['added']).pipe(unwrapChange); const modifiedChanges = sortedChanges(colRef, ['modified']).pipe( unwrapChange ); addedChanges.subscribe(data => { // kick off the modifiedChanges observable expect(data).to.eql(expectedEvents); davidDoc.update({ name: 'David!' }); }); modifiedChanges.subscribe(data => { const expectedModifiedEvent = [{ name: 'David!', type: 'modified' }]; expect(data).to.eql(expectedModifiedEvent); done(); }); }); }); describe('auditTrail', () => { /** * The `auditTrail()` method returns an array of every change that has * occurred in the application. This test seeds two "people" into the * collection and checks that the two added events are there. It then * modifies a "person" and makes sure that event is on the array as well. */ it('should keep create a list of all changes', (done: MochaDone) => { const { colRef, expectedEvents, davidDoc } = seedTest(firestore); const firstAudit = auditTrail(colRef).pipe(unwrapChange, take(1)); const secondAudit = auditTrail(colRef).pipe(unwrapChange, skip(1)); firstAudit.subscribe(list => { expect(list).to.eql(expectedEvents); davidDoc.update({ name: 'David!' }); }); secondAudit.subscribe(list => { const modifiedList = [ ...expectedEvents, { name: 'David!', type: 'modified' } ]; expect(list).to.eql(modifiedList); done(); }); }); /** * This test seeds two "people" into the collection. It then creates an * auditList observable that is filtered to 'modified' events. It modifies * a "person" document and ensures that list contains only the 'modified' * event. */ it('should filter the trail of events by event type', (done: MochaDone) => { const { colRef, davidDoc } = seedTest(firestore); const modifiedAudit = auditTrail(colRef, ['modified']).pipe(unwrapChange); modifiedAudit.subscribe(updateList => { const expectedEvents = [{ type: 'modified', name: 'David!' }]; expect(updateList).to.eql(expectedEvents); done(); }); davidDoc.update({ name: 'David!' }); }); }); describe('auditTrail', () => { /** * The `auditTrail()` method returns an array of every change that has * occurred in the application. This test seeds two "people" into the * collection and checks that the two added events are there. It then * modifies a "person" and makes sure that event is on the array as well. */ it('should keep create a list of all changes', (done: MochaDone) => { const { colRef, expectedEvents, davidDoc } = seedTest(firestore); const firstAudit = auditTrail(colRef).pipe(unwrapChange, take(1)); const secondAudit = auditTrail(colRef).pipe(unwrapChange, skip(1)); firstAudit.subscribe(list => { expect(list).to.eql(expectedEvents); davidDoc.update({ name: 'David!' }); }); secondAudit.subscribe(list => { const modifiedList = [ ...expectedEvents, { name: 'David!', type: 'modified' } ]; expect(list).to.eql(modifiedList); done(); }); }); /** * This test seeds two "people" into the collection. The wrap operator then converts */ it('should filter the trail of events by event type', (done: MochaDone) => { const { colRef, davidDoc } = seedTest(firestore); const modifiedAudit = auditTrail(colRef, ['modified']).pipe(unwrapChange); modifiedAudit.subscribe(updateList => { const expectedEvents = [{ type: 'modified', name: 'David!' }]; expect(updateList).to.eql(expectedEvents); done(); }); davidDoc.update({ name: 'David!' }); }); }); describe('Data Mapping Functions', () => { /** * The `unwrap(id)` method will map a collection to its data payload and map the doc ID to a the specificed key. */ it('collectionData should map a QueryDocumentSnapshot[] to an array of plain objects', (done: MochaDone) => { const { colRef } = seedTest(firestore); // const unwrapped = collection(colRef).pipe(unwrap('userId')); const unwrapped = collectionData(colRef, 'userId'); unwrapped.subscribe(val => { const expectedDoc = { name: 'David', userId: 'david' }; expect(val).to.be.instanceof(Array); expect(val[0]).to.eql(expectedDoc); done(); }); }); it('docData should map a QueryDocumentSnapshot to a plain object', (done: MochaDone) => { const { davidDoc } = seedTest(firestore); // const unwrapped = doc(davidDoc).pipe(unwrap('UID')); const unwrapped = docData(davidDoc, 'UID'); unwrapped.subscribe(val => { const expectedDoc = { name: 'David', UID: 'david' }; expect(val).to.eql(expectedDoc); done(); }); }); }); });