Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Start typing FirebaseAppProvider props, allow more than one app, erro…
…r on different config
  • Loading branch information
jamesdaniels committed Feb 12, 2020
commit 71adcb1391a6652328345bf3913caf9ddb3beb9c
2 changes: 1 addition & 1 deletion reactfire/auth/auth.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const mockFirebase = {
};

const Provider = ({ children }) => (
<FirebaseAppProvider firebaseApp={mockFirebase}>
<FirebaseAppProvider firebaseApp={(mockFirebase as any) as firebase.app.App}>
{children}
</FirebaseAppProvider>
);
Expand Down
67 changes: 59 additions & 8 deletions reactfire/firebaseApp/firebaseApp.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ import { FirebaseAppProvider } from './index';

afterEach(cleanup);

const DEFAULT_APP_CONFIG = { appId: '12345' };

describe('FirebaseAppProvider', () => {
it('calls firebase.initializeApp with the provided config', () => {
const config = { appId: '12345' };

const spy = jest.spyOn(firebase, 'initializeApp');

render(<FirebaseAppProvider firebaseConfig={config} />);
expect(spy).toBeCalledWith(config);
render(<FirebaseAppProvider firebaseConfig={DEFAULT_APP_CONFIG} />);
expect(spy).toBeCalledWith(DEFAULT_APP_CONFIG, undefined);

spy.mockRestore();
});

it('does not call firebase.initializeApp if the firebaseApp is provided', () => {
const spy = jest.spyOn(firebase, 'initializeApp');
const app = {};
const app: firebase.app.App = {} as any;
render(<FirebaseAppProvider firebaseApp={app} />);
expect(spy).not.toBeCalled();

Expand All @@ -32,7 +32,7 @@ describe('FirebaseAppProvider', () => {
it('initializes fireperf if specified', async () => {
const mockPerf = jest.fn();
firebase['performance' as any] = mockPerf;
const app = { performance: mockPerf };
const app: firebase.app.App = { performance: mockPerf } as any;

render(<FirebaseAppProvider firebaseApp={app} initPerformance />);

Expand All @@ -42,7 +42,7 @@ describe('FirebaseAppProvider', () => {
it('does not initialize fireperf if not specified', async () => {
const mockPerf = jest.fn();
firebase['performance' as any] = mockPerf;
const app = { performance: mockPerf };
const app: firebase.app.App = { performance: mockPerf } as any;

render(<FirebaseAppProvider firebaseApp={app} />);

Expand All @@ -52,7 +52,7 @@ describe('FirebaseAppProvider', () => {

describe('useFirebaseApp', () => {
it('finds firebase from Context', () => {
const firebaseApp = { a: 1 };
const firebaseApp: firebase.app.App = { a: 1 } as any;

const wrapper = ({ children }) => (
<FirebaseAppProvider firebaseApp={firebaseApp}>
Expand All @@ -65,6 +65,57 @@ describe('useFirebaseApp', () => {
expect(result.current).toBe(firebaseApp);
});

it('can initialize more than one firebase app', () => {
const config = { a: 1 };

const initializeApp = jest.spyOn(firebase, 'initializeApp');

const wrapper = ({ children }) => (
<div>
<FirebaseAppProvider firebaseConfig={DEFAULT_APP_CONFIG}>
{children}
</FirebaseAppProvider>
<FirebaseAppProvider firebaseConfig={config} appName="app-2">
appA
</FirebaseAppProvider>
</div>
);

const { result } = renderHook(() => useFirebaseApp(), { wrapper });

expect(initializeApp).toBeCalledWith(config, 'app-2');
initializeApp.mockRestore();

expect(result.error).toBeUndefined();
});

it('will throw if configs dont match, and same name', () => {
const config = { a: 1 };

const initializeApp = jest.spyOn(firebase, 'initializeApp');

const wrapper = ({ children }) => (
<div>
<FirebaseAppProvider firebaseConfig={DEFAULT_APP_CONFIG}>
{children}
</FirebaseAppProvider>
<FirebaseAppProvider firebaseConfig={config}>appA</FirebaseAppProvider>
</div>
);

try {
const { result } = renderHook(() => useFirebaseApp(), { wrapper });
fail('expected a throw');
} catch (e) {
expect(e).toEqual(
'Does not match the options already provided to the default firebase app instance, give this new instance a different appName.'
);
}

expect(initializeApp).not.toBeCalled();
initializeApp.mockRestore();
});

it('throws an error if Firebase is not in context', () => {
const { result } = renderHook(() => useFirebaseApp());

Expand Down
50 changes: 34 additions & 16 deletions reactfire/firebaseApp/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,54 @@ export * from './sdk';

type FirebaseAppContextValue = firebase.app.App;

// INVESTIGATE I don't like magic strings, can we have export this in js-sdk?
const DEFAULT_APP_NAME = '[DEFAULT]';

const FirebaseAppContext = React.createContext<
FirebaseAppContextValue | undefined
>(undefined);

export function FirebaseAppProvider(props) {
const { firebaseConfig, initPerformance } = props;
let { firebaseApp } = props;
type Props = {
initPerformance?: boolean;
firebaseApp?: firebase.app.App;
firebaseConfig?: Object;
appName?: string;
};

firebaseApp =
firebaseApp ||
export function FirebaseAppProvider(props: Props & { [key: string]: unknown }) {
const { firebaseConfig, appName, initPerformance = false } = props;
const firebaseApp: firebase.app.App =
props.firebaseApp ||
React.useMemo(() => {
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
const existingApp = firebase.apps.find(
app => app.name == (appName || DEFAULT_APP_NAME)
);
if (existingApp) {
// INVESTIGATE can we do a shallow eq check rather than JSON eq?
if (
JSON.stringify(existingApp.options) != JSON.stringify(firebaseConfig)
) {
throw `Does not match the options already provided to the ${appName ||
'default'} firebase app instance, give this new instance a different appName.`;
}
return existingApp;
} else {
return firebase.initializeApp(firebaseConfig, appName);
}

return firebase;
}, [firebaseConfig]);
}, [firebaseConfig, appName]);

React.useMemo(() => {
if (initPerformance === true && !!firebase.apps.length) {
if (!firebase.performance) {
if (initPerformance === true) {
if (firebaseApp.performance) {
// initialize Performance Monitoring
firebaseApp.performance();
} else {
throw new Error(
'firebase.performance not found. Did you forget to import it?'
);
}

// initialize Performance Monitoring
firebase.performance();
}
}, [initPerformance, firebaseApp]);
}, [initPerformance, appName]);

return <FirebaseAppContext.Provider value={firebaseApp} {...props} />;
}
Expand Down
10 changes: 8 additions & 2 deletions reactfire/firebaseApp/sdk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ function fetchSDK(
.then(() => settingsCallback(firebaseApp[sdk]))
.then(() => firebaseApp[sdk]);
}
preloadObservable(from(sdkPromise), `firebase-sdk-${sdk}`);
preloadObservable(
from(sdkPromise),
`firebase:sdk-${sdk}:${firebaseApp.name}`
);

return sdkPromise;
}
Expand All @@ -109,7 +112,10 @@ function useSDK(sdk: SDK, firebaseApp?: firebase.app.App) {
firebaseApp = firebaseApp || useFirebaseApp();

// use the request cache so we don't issue multiple fetches for the sdk
return useObservable(from(fetchSDK(sdk, firebaseApp)), `firebase-sdk-${sdk}`);
return useObservable(
from(fetchSDK(sdk, firebaseApp)),
`firebase:sdk-${sdk}:${firebaseApp.name}`
);
}

export function preloadAuth(
Expand Down
4 changes: 2 additions & 2 deletions reactfire/performance/performance.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ const mockPerf = jest.fn(() => {
return { trace: createTrace };
});

const mockFirebase = {
const mockFirebase: firebase.app.App = {
performance: mockPerf
};
} as any;

const PromiseThrower = () => {
throw new Promise((resolve, reject) => {});
Expand Down