UNPKG

voluptasmollitia

Version:
590 lines (503 loc) 20.9 kB
/** * @license * Copyright 2019 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 { fake, SinonSpy } from 'sinon'; import { ComponentContainer } from './component_container'; import { FirebaseService } from '@firebase/app-types/private'; // eslint-disable-next-line import/no-extraneous-dependencies import { _FirebaseService } from '@firebase/app-exp'; import { Provider } from './provider'; import { getFakeApp, getFakeComponent } from '../test/util'; import '../test/setup'; import { InstantiationMode } from './types'; // define the types for the fake services we use in the tests declare module './types' { interface NameServiceMapping { test: {}; badtest: {}; } } describe('Provider', () => { let provider: Provider<'test'>; beforeEach(() => { provider = new Provider('test', new ComponentContainer('test-container')); }); it('throws if setComponent() is called with a component with a different name than the provider name', () => { expect(() => // eslint-disable-next-line @typescript-eslint/no-explicit-any provider.setComponent(getFakeComponent('badtest', () => ({})) as any) ).to.throw(/^Mismatching Component/); expect(() => provider.setComponent(getFakeComponent('test', () => ({}))) ).to.not.throw(); }); it('does not throw if instance factory throws when calling getImmediate() with optional flag', () => { provider.setComponent( getFakeComponent('test', () => { throw Error('something went wrong!'); }) ); expect(() => provider.getImmediate({ optional: true })).to.not.throw(); }); it('throws if instance factory throws when calling getImmediate() without optional flag', () => { provider.setComponent( getFakeComponent('test', () => { throw Error('something went wrong!'); }) ); expect(() => provider.getImmediate()).to.throw(); }); it('does not throw if instance factory throws when calling get()', () => { provider.setComponent( getFakeComponent('test', () => { throw Error('something went wrong!'); }) ); expect(() => provider.get()).to.not.throw(); }); it('does not throw if instance factory throws when registering an eager component', () => { const eagerComponent = getFakeComponent( 'test', () => { throw Error('something went wrong!'); }, false, InstantiationMode.EAGER ); expect(() => provider.setComponent(eagerComponent)).to.not.throw(); }); it('does not throw if instance factory throws when registering a component with a pending promise', () => { // create a pending promise void provider.get(); const component = getFakeComponent('test', () => { throw Error('something went wrong!'); }); expect(() => provider.setComponent(component)).to.not.throw(); }); describe('initialize()', () => { it('throws if the provider is already initialized', () => { provider.setComponent(getFakeComponent('test', () => ({}))); provider.initialize(); expect(() => provider.initialize()).to.throw(); }); it('throws if the component has not been registered', () => { expect(() => provider.initialize()).to.throw(); }); it('accepts an options parameter and passes it to the instance factory', () => { const options = { configurable: true, test: true }; provider.setComponent( getFakeComponent('test', (_container, opts) => ({ options: opts.options })) ); const instance = provider.initialize({ options }); expect((instance as any).options).to.deep.equal(options); }); it('resolve pending promises created by Provider.get() with the same identifier', () => { provider.setComponent( getFakeComponent( 'test', () => ({ test: true }), false, InstantiationMode.EXPLICIT ) ); const servicePromise = provider.get(); expect((provider as any).instances.size).to.equal(0); provider.initialize(); expect((provider as any).instances.size).to.equal(1); return expect(servicePromise).to.eventually.deep.equal({ test: true }); }); it('invokes onInit callbacks synchronously', () => { provider.setComponent( getFakeComponent( 'test', () => ({ test: true }), false, InstantiationMode.EXPLICIT ) ); const callback1 = fake(); provider.onInit(callback1); provider.initialize(); expect(callback1).to.have.been.calledOnce; }); }); describe('onInit', () => { it('registers onInit callbacks', () => { provider.setComponent( getFakeComponent( 'test', () => ({ test: true }), false, InstantiationMode.EXPLICIT ) ); const callback1 = fake(); const callback2 = fake(); provider.onInit(callback1); provider.onInit(callback2); provider.initialize(); expect(callback1).to.have.been.calledOnce; expect(callback2).to.have.been.calledOnce; }); it('returns a function to unregister the callback', () => { provider.setComponent( getFakeComponent( 'test', () => ({ test: true }), false, InstantiationMode.EXPLICIT ) ); const callback1 = fake(); const callback2 = fake(); provider.onInit(callback1); const unregsiter = provider.onInit(callback2); unregsiter(); provider.initialize(); expect(callback1).to.have.been.calledOnce; expect(callback2).to.not.have.been.called; }); }); describe('Provider (multipleInstances = false)', () => { describe('getImmediate()', () => { it('throws if component has not been registered', () => { expect(provider.getImmediate.bind(provider)).to.throw( 'Service test is not available' ); }); it('returns null with the optional flag set if component has not been registered ', () => { expect(provider.getImmediate({ optional: true })).to.equal(null); }); it('returns the service instance synchronously', () => { provider.setComponent(getFakeComponent('test', () => ({ test: true }))); expect(provider.getImmediate()).to.deep.equal({ test: true }); }); it('returns the cached service instance', () => { provider.setComponent(getFakeComponent('test', () => ({ test: true }))); const service1 = provider.getImmediate(); const service2 = provider.getImmediate(); expect(service1).to.equal(service2); }); it('ignores parameter identifier and return the default service', () => { provider.setComponent(getFakeComponent('test', () => ({ test: true }))); const defaultService = provider.getImmediate(); expect(provider.getImmediate({ identifier: 'spider1' })).to.equal( defaultService ); expect(provider.getImmediate({ identifier: 'spider2' })).to.equal( defaultService ); }); }); describe('get()', () => { it('get the service instance asynchronouly', async () => { provider.setComponent(getFakeComponent('test', () => ({ test: true }))); await expect(provider.get()).to.eventually.deep.equal({ test: true }); }); it('ignore parameter identifier and return the default service instance asyn', async () => { provider.setComponent(getFakeComponent('test', () => ({ test: true }))); const defaultService = provider.getImmediate(); await expect(provider.get('spider1')).to.eventually.equal( defaultService ); await expect(provider.get('spider2')).to.eventually.equal( defaultService ); }); }); describe('setComponent()', () => { it('instantiates the service if there is a pending promise and the service is eager', () => { // create a pending promise void provider.get(); provider.setComponent( getFakeComponent('test', () => ({}), false, InstantiationMode.EAGER) ); expect((provider as any).instances.size).to.equal(1); }); it('instantiates the service if there is a pending promise and the service is NOT eager', () => { // create a pending promise void provider.get(); provider.setComponent(getFakeComponent('test', () => ({}))); expect((provider as any).instances.size).to.equal(1); }); it('instantiates the service if there is no pending promise and the service is eager', () => { provider.setComponent( getFakeComponent('test', () => ({}), false, InstantiationMode.EAGER) ); expect((provider as any).instances.size).to.equal(1); }); it('does NOT instantiate the service if there is no pending promise and the service is not eager', () => { provider.setComponent(getFakeComponent('test', () => ({}))); expect((provider as any).instances.size).to.equal(0); }); it('instantiates only the default service even if there are pending promises with identifiers', async () => { // create a pending promise with identifiers. const promise1 = provider.get('name1'); const promise2 = provider.get('name2'); provider.setComponent(getFakeComponent('test', () => ({}))); expect((provider as any).instances.size).to.equal(1); const defaultService = provider.getImmediate(); await expect(promise1).to.eventually.equal(defaultService); await expect(promise2).to.eventually.equal(defaultService); }); }); describe('delete()', () => { it('calls delete() on the service instance that implements legacy FirebaseService', () => { const deleteFake = fake(); const myService: FirebaseService = { app: getFakeApp(), INTERNAL: { delete: deleteFake } }; // provide factory and create a service instance provider.setComponent( getFakeComponent( 'test', () => myService, false, InstantiationMode.EAGER ) ); void provider.delete(); expect(deleteFake).to.have.been.called; }); it('calls delete() on the service instance that implements next FirebaseService', () => { const deleteFake = fake(); const myService: _FirebaseService = { app: getFakeApp(), _delete: deleteFake }; // provide factory and create a service instance provider.setComponent( getFakeComponent( 'test', () => myService, false, InstantiationMode.EAGER ) ); void provider.delete(); expect(deleteFake).to.have.been.called; }); }); describe('clearCache()', () => { it('removes the service instance from cache', () => { provider.setComponent(getFakeComponent('test', () => ({}))); // create serviec instance const instance = provider.getImmediate(); expect((provider as any).instances.size).to.equal(1); provider.clearInstance(); expect((provider as any).instances.size).to.equal(0); // get a new instance after cache has been cleared const newInstance = provider.getImmediate(); expect(newInstance).to.not.eq(instance); }); }); }); describe('Provider (multipleInstances = true)', () => { describe('getImmediate(identifier)', () => { it('throws if the service is not available', () => { expect( provider.getImmediate.bind(provider, { identifier: 'guardian' }) ).to.throw(); }); it('returns null if the service is not available with optional flag', () => { expect( provider.getImmediate({ identifier: 'guardian', optional: true }) ).to.equal(null); }); it('returns different service instances for different identifiers synchronously', () => { provider.setComponent( getFakeComponent('test', () => ({ test: true }), true) ); const defaultService = provider.getImmediate(); const service1 = provider.getImmediate({ identifier: 'guardian' }); const service2 = provider.getImmediate({ identifier: 'servant' }); expect(defaultService).to.deep.equal({ test: true }); expect(service1).to.deep.equal({ test: true }); expect(service2).to.deep.equal({ test: true }); expect(defaultService).to.not.equal(service1); expect(defaultService).to.not.equal(service2); expect(service1).to.not.equal(service2); }); }); describe('get(identifier)', () => { it('returns different service instances for different identifiers asynchronouly', async () => { provider.setComponent( getFakeComponent('test', () => ({ test: true }), true) ); const defaultService = await provider.get(); const service1 = await provider.get('name1'); const service2 = await provider.get('name2'); expect(defaultService).to.deep.equal({ test: true }); expect(service1).to.deep.equal({ test: true }); expect(service2).to.deep.equal({ test: true }); expect(defaultService).to.not.equal(service1); expect(defaultService).to.not.equal(service2); expect(service1).to.not.equal(service2); }); }); describe('setComponent()', () => { it('instantiates services for the pending promises for all instance identifiers', async () => { // create 3 promises for 3 different identifiers void provider.get(); void provider.get('name1'); void provider.get('name2'); provider.setComponent( getFakeComponent('test', () => ({ test: true }), true) ); expect((provider as any).instances.size).to.equal(3); }); it('instantiates the default service if there is no pending promise and the service is eager', () => { provider.setComponent( getFakeComponent( 'test', () => ({ test: true }), true, InstantiationMode.EAGER ) ); expect((provider as any).instances.size).to.equal(1); }); it(`instantiates the default serviec if there are pending promises for other identifiers but not for the default identifer and the service is eager`, () => { void provider.get('name1'); provider.setComponent( getFakeComponent( 'test', () => ({ test: true }), true, InstantiationMode.EAGER ) ); expect((provider as any).instances.size).to.equal(2); }); }); describe('delete()', () => { it('calls delete() on all service instances that implement FirebaseService', () => { const deleteFakes: SinonSpy[] = []; function getService(): FirebaseService { const deleteFake = fake(); deleteFakes.push(deleteFake); return { app: getFakeApp(), INTERNAL: { delete: deleteFake } }; } // provide factory that produces mulitpleInstances provider.setComponent(getFakeComponent('test', getService, true)); // create 2 service instances with different names provider.getImmediate({ identifier: 'instance1' }); provider.getImmediate({ identifier: 'instance2' }); void provider.delete(); expect(deleteFakes.length).to.equal(2); for (const f of deleteFakes) { expect(f).to.have.been.called; } }); }); describe('clearCache()', () => { it('returns new service instances sync after cache is cleared', () => { provider.setComponent(getFakeComponent('test', () => ({}), true)); // create serviec instances with different identifiers const defaultInstance = provider.getImmediate(); const instance1 = provider.getImmediate({ identifier: 'instance1' }); expect((provider as any).instances.size).to.equal(2); // remove the default instance from cache and create a new default instance provider.clearInstance(); expect((provider as any).instances.size).to.equal(1); const newDefaultInstance = provider.getImmediate(); expect(newDefaultInstance).to.not.eq(defaultInstance); expect((provider as any).instances.size).to.equal(2); // remove the named instance from cache and create a new instance with the same identifier provider.clearInstance('instance1'); expect((provider as any).instances.size).to.equal(1); const newInstance1 = provider.getImmediate({ identifier: 'instance1' }); expect(newInstance1).to.not.eq(instance1); expect((provider as any).instances.size).to.equal(2); }); it('returns new services asynchronously after cache is cleared', async () => { provider.setComponent(getFakeComponent('test', () => ({}), true)); // create serviec instances with different identifiers const defaultInstance = await provider.get(); const instance1 = await provider.get('instance1'); expect((provider as any).instances.size).to.equal(2); expect((provider as any).instancesDeferred.size).to.equal(2); // remove the default instance from cache and create a new default instance provider.clearInstance(); expect((provider as any).instances.size).to.equal(1); expect((provider as any).instancesDeferred.size).to.equal(1); const newDefaultInstance = await provider.get(); expect(newDefaultInstance).to.not.eq(defaultInstance); expect((provider as any).instances.size).to.equal(2); expect((provider as any).instancesDeferred.size).to.equal(2); // remove the named instance from cache and create a new instance with the same identifier provider.clearInstance('instance1'); expect((provider as any).instances.size).to.equal(1); expect((provider as any).instancesDeferred.size).to.equal(1); const newInstance1 = await provider.get('instance1'); expect(newInstance1).to.not.eq(instance1); expect((provider as any).instances.size).to.equal(2); expect((provider as any).instancesDeferred.size).to.equal(2); }); }); }); describe('InstantiationMode: EXPLICIT', () => { it('setComponent() does NOT auto-initialize the service', () => { // create a pending promise which should trigger initialization if instantiationMode is non-EXPLICIT void provider.get(); provider.setComponent( getFakeComponent('test', () => ({}), false, InstantiationMode.EXPLICIT) ); expect((provider as any).instances.size).to.equal(0); }); it('get() does NOT auto-initialize the service', () => { provider.setComponent( getFakeComponent('test', () => ({}), false, InstantiationMode.EXPLICIT) ); expect((provider as any).instances.size).to.equal(0); void provider.get(); expect((provider as any).instances.size).to.equal(0); }); it('getImmediate() does NOT auto-initialize the service and throws if the service has not been initialized', () => { provider.setComponent( getFakeComponent('test', () => ({}), false, InstantiationMode.EXPLICIT) ); expect((provider as any).instances.size).to.equal(0); expect(() => provider.getImmediate()).to.throw( 'Service test is not available' ); expect((provider as any).instances.size).to.equal(0); }); it('getImmediate() does NOT auto-initialize the service and returns null if the optional flag is set', () => { provider.setComponent( getFakeComponent('test', () => ({}), false, InstantiationMode.EXPLICIT) ); expect((provider as any).instances.size).to.equal(0); expect(provider.getImmediate({ optional: true })).to.equal(null); expect((provider as any).instances.size).to.equal(0); }); }); });