Skip to content

Commit 52f3bc1

Browse files
committed
add tests
1 parent 373b1e3 commit 52f3bc1

File tree

5 files changed

+284
-67
lines changed

5 files changed

+284
-67
lines changed

reactfire/components.test.tsx

Lines changed: 204 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,220 @@
11
import { of, Subject, Observable, observable } from 'rxjs';
2-
import { render, waitForElement, cleanup } from '@testing-library/react';
2+
import { render, waitForElement, cleanup, act } from '@testing-library/react';
33
import * as React from 'react';
44
import 'jest-dom/extend-expect';
5+
import { SuspenseWithPerf, AuthCheck } from './components';
6+
import { FirebaseAppProvider } from './firebaseContext';
7+
import { auth, performance, User } from 'firebase/app';
58

6-
const mockTrace = {
7-
start: jest.fn(),
8-
stop: jest.fn()
9-
};
10-
11-
const mockPerf = {
12-
trace: jest.fn(() => mockTrace)
13-
};
14-
15-
const mockFirebase = {
16-
performance: jest.fn(() => mockPerf)
17-
};
18-
19-
describe('SuspenseWithPerf', () => {
20-
afterEach(cleanup);
9+
const traceStart = jest.fn();
10+
const traceEnd = jest.fn();
2111

22-
test.todo(
23-
'renders the same as Suspense (fallback until children stop throwing a promise)'
24-
);
12+
const createTrace = jest.fn(() => ({
13+
start: traceStart,
14+
stop: traceEnd
15+
}));
2516

26-
test.todo('creates a trace with the correct name');
17+
const mockPerf = jest.fn(() => {
18+
return { trace: createTrace };
19+
});
2720

28-
test.todo('starts a trace when it first renders');
21+
const mockAuth = jest.fn(() => {
22+
return {
23+
onIdTokenChanged: jest.fn()
24+
};
25+
});
2926

30-
test.todo('stops the trace when it renders children');
27+
const mockFirebase = {
28+
performance: mockPerf,
29+
auth: mockAuth
30+
};
3131

32-
test.todo('can find fireperf from Context');
32+
const Provider = ({ children }) => (
33+
<FirebaseAppProvider firebaseApp={mockFirebase}>
34+
{children}
35+
</FirebaseAppProvider>
36+
);
3337

34-
test.todo('can use firePerf from props');
38+
describe('SuspenseWithPerf', () => {
39+
afterEach(() => {
40+
cleanup();
41+
jest.clearAllMocks();
42+
});
43+
44+
it('behaves the same as Suspense (render fallback until thrown promise resolves)', async () => {
45+
const o$ = new Subject();
46+
let shouldThrow = true;
47+
48+
const promise = new Promise((resolve, reject) => {
49+
o$.subscribe(() => {
50+
shouldThrow = false;
51+
resolve();
52+
});
53+
});
54+
55+
const Fallback = () => <h1 data-testid="fallback">Fallback</h1>;
56+
57+
const Comp = () => {
58+
if (shouldThrow) {
59+
throw promise;
60+
}
61+
62+
return <h1 data-testid="child">Actual</h1>;
63+
};
64+
65+
const SuspenseComp = () => {
66+
return (
67+
<React.Suspense fallback={<Fallback />}>
68+
<Comp />
69+
</React.Suspense>
70+
);
71+
};
72+
73+
const SuspenseWithPerfComp = () => {
74+
return (
75+
<Provider>
76+
<SuspenseWithPerf fallback={<Fallback />} traceId="test">
77+
<Comp />
78+
</SuspenseWithPerf>
79+
</Provider>
80+
);
81+
};
82+
83+
const { queryAllByTestId, getAllByTestId } = render(
84+
<>
85+
<SuspenseComp />
86+
<SuspenseWithPerfComp />
87+
</>
88+
);
89+
90+
expect(queryAllByTestId('fallback').length).toEqual(2);
91+
expect(queryAllByTestId('child').length).toEqual(0);
92+
93+
act(() => o$.next('a'));
94+
await waitForElement(() => getAllByTestId('child'));
95+
96+
expect(queryAllByTestId('fallback').length).toEqual(0);
97+
expect(queryAllByTestId('child').length).toEqual(2);
98+
99+
// expect(suspense.innerHTML).toEqual('Fallback');
100+
});
101+
102+
it('creates a trace with the correct name', () => {
103+
const traceName = 'trace';
104+
render(
105+
<Provider>
106+
<SuspenseWithPerf traceId={traceName} fallback={'loading'}>
107+
children
108+
</SuspenseWithPerf>
109+
</Provider>
110+
);
111+
112+
expect(createTrace).toHaveBeenCalledWith(traceName);
113+
});
114+
115+
it('starts a trace when a promise is thrown and stops when it resolves', async () => {
116+
const o$ = new Subject();
117+
let shouldThrow = true;
118+
119+
const promise = new Promise((resolve, reject) => {
120+
o$.subscribe(() => {
121+
shouldThrow = false;
122+
resolve();
123+
});
124+
});
125+
126+
const Fallback = () => <h1 data-testid="fallback">Fallback</h1>;
127+
128+
const Comp = () => {
129+
if (shouldThrow) {
130+
throw promise;
131+
}
132+
133+
return <h1 data-testid="child">Actual</h1>;
134+
};
135+
136+
const { getByTestId } = render(
137+
<Provider>
138+
<SuspenseWithPerf fallback={<Fallback />} traceId="test lifecycle">
139+
<Comp />
140+
</SuspenseWithPerf>
141+
</Provider>
142+
);
143+
144+
expect(getByTestId('fallback')).toBeInTheDocument();
145+
146+
expect(traceStart).toHaveBeenCalledTimes(1);
147+
expect(traceEnd).toHaveBeenCalledTimes(0);
148+
149+
act(() => o$.next('a'));
150+
await waitForElement(() => getByTestId('child'));
151+
expect(getByTestId('child')).toBeInTheDocument();
152+
153+
expect(traceStart).toHaveBeenCalledTimes(1);
154+
expect(traceEnd).toHaveBeenCalledTimes(1);
155+
});
156+
157+
it('can find fireperf from Context', () => {
158+
render(
159+
<Provider>
160+
<SuspenseWithPerf traceId={'hello'} fallback={'loading'}>
161+
{'children'}
162+
</SuspenseWithPerf>
163+
</Provider>
164+
);
165+
166+
expect(mockPerf).toHaveBeenCalled();
167+
});
168+
169+
it('can use firePerf from props', () => {
170+
render(
171+
<SuspenseWithPerf
172+
traceId={'hello'}
173+
fallback={'loading'}
174+
firePerf={mockPerf()}
175+
>
176+
{'children'}
177+
</SuspenseWithPerf>
178+
);
179+
180+
expect(createTrace).toHaveBeenCalled();
181+
});
35182
});
36183

37184
describe('AuthCheck', () => {
38-
afterEach(cleanup);
39-
40-
test.todo('can find firebase Auth from Context');
41-
42-
test.todo('can use firebase Auth from props');
185+
afterEach(() => {
186+
cleanup();
187+
jest.clearAllMocks();
188+
});
189+
190+
it('can find firebase Auth from Context', () => {
191+
expect(() =>
192+
render(
193+
<Provider>
194+
<React.Suspense fallback={'loading'}>
195+
<AuthCheck fallback={'loading'}>{'children'}</AuthCheck>
196+
</React.Suspense>
197+
</Provider>
198+
)
199+
).not.toThrow();
200+
});
201+
202+
it('can use firebase Auth from props', () => {
203+
expect(() =>
204+
render(
205+
<React.Suspense fallback={'loading'}>
206+
<AuthCheck
207+
fallback={'not signed in'}
208+
auth={(mockFirebase.auth() as unknown) as auth.Auth}
209+
>
210+
{'signed in'}
211+
</AuthCheck>
212+
</React.Suspense>
213+
)
214+
).not.toThrow();
215+
216+
// if this doesn't throw, it's good
217+
});
43218

44219
test.todo('renders the fallback if a user is not signed in');
45220

reactfire/components.tsx

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import * as React from 'react';
2-
import { auth, performance, User } from 'firebase/app';
2+
import { firebase, auth, performance, User } from 'firebase/app';
33
import { useUser, useFirebaseApp } from './index';
44
const { Suspense, useState, useLayoutEffect } = React;
55

66
export interface SuspensePerfProps {
77
children: React.ReactNode;
88
traceId: string;
99
fallback: React.ReactNode;
10-
firePerf?: any; // TODO(jeff): Add firePerf here when it's available
10+
firePerf?: firebase.performance.Performance; // TODO(jeff): Add firePerf here when it's available
1111
}
1212

1313
function getPerfFromContext(): performance.Performance {
@@ -23,7 +23,7 @@ function getPerfFromContext(): performance.Performance {
2323

2424
if (!perfFunc || !perfFunc()) {
2525
throw new Error(
26-
"No auth object off of Firebase. Did you forget to import 'firebase/auth' in a component?"
26+
"No auth object off of Firebase. Did you forget to import 'firebase/performance' in a component?"
2727
);
2828
}
2929

@@ -37,38 +37,21 @@ export function SuspenseWithPerf({
3737
firePerf
3838
}: SuspensePerfProps) {
3939
firePerf = firePerf || getPerfFromContext();
40-
const [trace, setTrace] = useState(null);
41-
const [traceStarted, setTraceStarted] = useState(false);
42-
const [traceCompleted, setTraceCompleted] = useState(false);
40+
const trace = React.useMemo(() => firePerf.trace(traceId), [traceId]);
4341

4442
const Fallback = () => {
4543
useLayoutEffect(() => {
46-
if (!trace) {
47-
setTrace(firePerf.trace(traceId));
48-
}
44+
trace.start();
4945

50-
if (trace && !traceStarted) {
51-
trace.start();
52-
console.log(`started trace ${traceId}`);
53-
setTraceStarted(true);
54-
}
55-
});
46+
return () => {
47+
trace.stop();
48+
};
49+
}, []);
5650

5751
return <>{fallback}</>;
5852
};
5953

60-
const Children = () => {
61-
useLayoutEffect(() => {
62-
if (trace && traceStarted && !traceCompleted) {
63-
trace.stop();
64-
console.log(`stopped trace ${traceId}`);
65-
setTraceCompleted(true);
66-
}
67-
});
68-
69-
return <>{children}</>;
70-
};
71-
return <Suspense fallback={<Fallback />}>{<Children />}</Suspense>;
54+
return <Suspense fallback={<Fallback />}>{children}</Suspense>;
7255
}
7356

7457
export interface AuthCheckProps {

reactfire/firebaseContext.test.tsx

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,73 @@
11
import { renderHook, act } from '@testing-library/react-hooks';
2+
import { render, cleanup } from '@testing-library/react';
23
import * as React from 'react';
34
import 'jest-dom/extend-expect';
5+
import { FirebaseAppProvider } from './index';
6+
import * as firebase from 'firebase/app';
7+
import { useFirebaseApp } from './firebaseContext';
8+
9+
afterEach(cleanup);
410

511
describe('FirebaseAppProvider', () => {
6-
test.todo('calls firebase.initializeApp');
12+
it('calls firebase.initializeApp with the provided config', () => {
13+
const config = { appId: '12345' };
14+
15+
const spy = jest.spyOn(firebase, 'initializeApp');
16+
17+
render(<FirebaseAppProvider firebaseConfig={config} />);
18+
expect(spy).toBeCalledWith(config);
19+
20+
spy.mockRestore();
21+
});
22+
23+
it('does not call firebase.initializeApp if the firebaseApp is provided', () => {
24+
const spy = jest.spyOn(firebase, 'initializeApp');
25+
const app = {};
26+
render(<FirebaseAppProvider firebaseApp={app} />);
27+
expect(spy).not.toBeCalled();
28+
29+
spy.mockRestore();
30+
});
31+
32+
it('initializes fireperf if specified', async () => {
33+
const mockPerf = jest.fn();
34+
firebase['performance' as any] = mockPerf;
35+
const app = { performance: mockPerf };
736

8-
test.todo('initializes fireperf if specified');
37+
render(<FirebaseAppProvider firebaseApp={app} initPerformance />);
38+
39+
expect(mockPerf).toBeCalled();
40+
});
41+
42+
it('does not initialize fireperf if not specified', async () => {
43+
const mockPerf = jest.fn();
44+
firebase['performance' as any] = mockPerf;
45+
const app = { performance: mockPerf };
46+
47+
render(<FirebaseAppProvider firebaseApp={app} />);
48+
49+
expect(mockPerf).not.toBeCalled();
50+
});
951
});
1052

1153
describe('useFirebaseApp', () => {
12-
test.todo('finds firebase from Context');
54+
it('finds firebase from Context', () => {
55+
const firebaseApp = { a: 1 };
56+
57+
const wrapper = ({ children }) => (
58+
<FirebaseAppProvider firebaseApp={firebaseApp}>
59+
{children}
60+
</FirebaseAppProvider>
61+
);
62+
63+
const { result } = renderHook(() => useFirebaseApp(), { wrapper });
64+
expect(result.error).toBeUndefined();
65+
expect(result.current).toBe(firebaseApp);
66+
});
67+
68+
it('throws an error if Firebase is not in context', () => {
69+
const { result } = renderHook(() => useFirebaseApp());
1370

14-
test.todo('throws an error if Firebase is not in context');
71+
expect(result.error).toBeDefined();
72+
});
1573
});

0 commit comments

Comments
 (0)