React Testing Error: Jest encountered an unexpected token - reactjs

I am conducting test using the React Testing Library with Jest. I have a test file as such:
import React from "react";
import { render as rtlRender, fireEvent } from "#testing-library/react";
import Component from "./..";
import { data, userId } from "./testConstants";
import { Provider } from "react-redux";
import store from "redux/store";
const render = (component) =>
rtlRender(<Provider store={store}>{component}</Provider>);
describe(InvoiceList, () => {
it("Number of invoice list item is rendered correctly according to search input", () => {
const { getByTestId } = render(<Component userId={userId} data={data} />);
fireEvent.change(getByTestId("search-input"), {
target: { value: "bacardi" },
});
fireEvent.click(getByTestId("all-tab"));
expect(queryAllByTestId("invoice-list-item")).toHaveLength(3);
});
});
I ran the test, but got an error where Jest encountered an unexpected token. This error has details stating SyntaxError: await is only valid in async function and shows me that the error occur from an import in import { auth } from "services/firebase".
services/firebase.ts
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore, initializeFirestore } from "firebase/firestore";
import { getStorage } from "firebase/storage";
export const isLocalhost = Boolean(
window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address.
window.location.hostname === "[::1]" ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
const config = {
development: {
...
},
production: {
...
},
};
const firebaseConfig = isLocalhost
? config.development
: await fetch("/__/firebase/init.json").then((response) => response.json()); // THE ERROR DETAILS SHOW THAT THE ERROR IS FROM THIS LINE OF CODE
// eslint-disable-next-line max-len
// This will use our config to recognize the project and initialize authentication and database modules.
export const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
initializeFirestore(app, {
ignoreUndefinedProperties: true,
});
export const db = getFirestore(app);
export const storage = getStorage(app);
I'm not sure what is wrong right here, the firebase auth can be used correctly during development and production but I'm getting the error during testing.

Related

Firestore security rules - `request.auth` is always null in react and next.js

Firebase version: 9.15.0
React version: 18.2.0
Next.js version: 13.1.1
Jotai version: 1.12.1
I'm trying to make a request to my firestore database. I am logged in, and I can print my uid to console fine. In the FirebaseError error, it mentions my method updateUserLicense. I'm using Jotai for state management. It's interesting that the atoms are undefined when set in onAuthStateChanged. However, I don't use them in updateUserLicense, so it shouldn't be an issue.
I have a file that initializes firebase and it's respective services, and then those are exported and imported by the javascript files that contain functions such as updateUserLicense. This file is under the "services" folder:
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
apiKey: "AIzaSyB-ZPDz7RI6j07bxqfqcQqvuJC8CcQ5QFI",
authDomain: "ai-docs-tool.firebaseapp.com",
projectId: "ai-docs-tool",
storageBucket: "ai-docs-tool.appspot.com",
messagingSenderId: "57784220238",
appId: "1:57784220238:web:18bd4a9bc3843b5451611f",
measurementId: "G-0L2LR8LHX1"
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
export {auth, db};
export * from "./authentication.service";
export * from "./license.service";
Login button component:
import React from 'react';
import { useAtom } from "jotai";
import { onAuthStateChanged } from "firebase/auth";
import { loginWithGoogle, updateUserLicense, auth } from "../services";
import { userAtom, licensesAtom } from "../state";
export default function LoginButton() {
const [user, setUser] = useAtom(userAtom);
const [licenses, setLicenses] = useAtom(licensesAtom);
onAuthStateChanged(auth, async (newUser) => {
if(newUser && user == null) {
setUser(newUser);
const license = await updateUserLicense(newUser);
setLicenses(license);
}
});
return (
<button onClick={loginWithGoogle}>Login</button>
)
}
Troublesome update function, in a separate file under a folder called "services":
import {db} from "./index";
import { getFirestore, doc, getDoc, setDoc, serverTimestamp, updateDoc } from "firebase/firestore";
import { v4 as uuidv4 } from "uuid";
import { auth } from "./index";
export async function updateUserLicense(user) {
if(user == null) return;
console.log("UID:", user.uid); // Prints the uid passed in fine, so user is authenticated
const docRef = doc(db, "license-keys", user.uid); // All of this code works when the security rules are read/write enabled for all.
const docSnap = await getDoc(docRef);
if(docSnap.exists()) {
updateDoc(docRef, {lastRead: serverTimestamp()});
return docSnap.data().licenses;
}
else {
const newLicense = uuidv4();
setDoc(docRef, {licenses: [newLicense], lastRead: serverTimestamp()});
return newLicense;
}
}
Security rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /license-keys/{id} {
allow read: if request.auth != null;
allow create: if request.auth != null;
}
}
}
My database, and the rules playground that shows it working if I pass in the uid by hand:
I've tried using the rules playground, but it always passes with the uid that I use.
I tried printing the user.uid to see if it's there, and it is.
I've tried logging out, and then in and again, and it still doesn't work.
I tried downgrading to v8, but it just broke my project.

#firebase/firestore: Firestore (9.6.8): Could not reach Cloud Firestore backend. Backend didn't respond within 10 seconds. Error react-native AVD

I've been getting this error working on react-native. Firebase connects normally, with auth(), but I can't seem to get it working for firestore. The problem only occurred on Android Emulator, the iOS one is working fine.
error only occurred on Android Virtual Device, the iOS one work perfectly
Here is Firebase file (removed some information about the app)
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
apiKey: '',
authDomain: '',
projectId: '',
storageBucket: '',
messagingSenderId: '',
appId: '',
measurementId: '',
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const auth = getAuth();
const db = getFirestore(app);
export { auth, db };
Here is where I used Firestore function
import React, { useState } from 'react';
import { useTailwind } from 'tailwind-rn/dist';
import useAuth from '../../hooks/useAuth';
import { doc, serverTimestamp, setDoc } from 'firebase/firestore';
import { db } from '../../Firebase';
import { useNavigation } from '#react-navigation/native';
const ModalScreen = () => {
const tw = useTailwind();
const [image, setImage] = useState('');
const [job, setJob] = useState('');
const [age, setAge] = useState('');
const navigation = useNavigation();
const inCompleteForm = !image || !job || !age;
const updateUserProfile = () => {
setDoc(doc(db, 'users', user.uid), {
id: user.uid,
displayName: user.displayName,
photoURL: image,
job,
age,
timestamp: serverTimestamp(),
})
.then(() => {
navigation.navigate('Home');
})
.catch(err => {
alert(err.message);
});
};
import {
View,
Text,
SafeAreaView,
TouchableOpacity,
Image,
StyleSheet,
Platform,
} from 'react-native';
import React, { useRef, useState, useLayoutEffect } from 'react';
import { useTailwind } from 'tailwind-rn/dist';
import { useNavigation } from '#react-navigation/native';
import useAuth from '../../hooks/useAuth';
import Icon from 'react-native-vector-icons/Ionicons';
import Swiper from 'react-native-deck-swiper';
import { doc, onSnapshot } from 'firebase/firestore';
import { db } from '../../Firebase';
const HomeScreen = () => {
const navigation = useNavigation();
const tw = useTailwind();
const [profiles, setProfiles] = useState([]);
const { user, logOut } = useAuth();
const swipeRef = useRef(null);
useLayoutEffect(() => {
onSnapshot(doc(db, 'users', user.uid), snapshot => {
if (!snapshot.exists) navigation.navigate('Modal');
});
}, []);
Error
[2022-03-15T06:49:42.961Z] #firebase/firestore: Firestore (9.6.8): Could not reach Cloud Firestore backend. Backend didn't respond within 10 seconds.
This typically indicates that your device does not have a healthy Internet connection at the moment. The client will operate in offline mode until it is able to successfully connect to the backend.
WARN [2022-03-15T06:53:06.868Z] #firebase/firestore: Firestore (9.6.8): Connection WebChannel transport errored: {"defaultPrevented": false,
i got the same problem, try to see here or the code i will give bellow if it can solve the problem :)
import { initializeFirestore } from 'firebase/firestore'
const database = initializeFirestore(app, {
experimentalAutoDetectLongPolling: true
})
const "database" in my example is not the same as "db" in your code, keep the both

Testing with Jest while using MSW and RTK Query leads to strange error in test

I have been spending most of the day trying to sort out this insanely annoying bug.
I am using redux-toolkit, MSW, RTK query, and React Testing Libary and am currently busy writing an integration test that tests a simple login flow.
The problem I have is that I am testing two different scenarios in one test suite, one is a successful login and one is a failed one.
When I run one at a time, I get no problems, but when when I run both, I get the following error for the failed scenario.
TypeError: Cannot convert undefined or null to object
at Function.values (<anonymous>)
59 | (state, action) => {
60 | const { payload } = action;
> 61 | adapter.upsertMany(state, payload);
| ^
62 | }
63 | );
64 | },
at ensureEntitiesArray (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:794:27)
at splitAddedUpdatedEntities (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:799:19)
at upsertManyMutably (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:911:18)
at runMutator (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:772:17)
at Object.upsertMany (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:776:13)
at src/features/customers/store/customersSlice.ts:61:17
at recipe (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:663:32)
at Immer.produce (node_modules/immer/src/core/immerClass.ts:94:14)
at node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:662:54
at Array.reduce (<anonymous>)
at node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:641:29
at combination (node_modules/redux/lib/redux.js:536:29)
at dispatch (node_modules/redux/lib/redux.js:296:22)
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1366:26
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1264:26
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1224:22
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1138:26
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1087:22
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1049:26
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1424:26
at node_modules/#reduxjs/toolkit/dist/query/rtk-query.cjs.development.js:1458:24
at node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:446:22
at node_modules/redux-thunk/lib/index.js:14:16
at node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:374:36
at dispatch (node_modules/redux/lib/redux.js:667:28)
at node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:1204:37
at step (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:38:23)
at Object.next (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:19:53)
at fulfilled (node_modules/#reduxjs/toolkit/dist/redux-toolkit.cjs.development.js:97:32)
What makes this strange is that the failed scenario isn't supposed to get to the page that calls the API call that results in this extra reducer matcher, hence why there is no payload and the error happens.
This doesn't happen when I test in the browser, only when testing with Jest.
Below are my tests:
import React from "react";
import { render, screen, waitFor, cleanup } from "./test-utils";
import App from "../App";
import userEvent from "#testing-library/user-event";
import { waitForElementToBeRemoved } from "#testing-library/react";
import { configureStore } from "#reduxjs/toolkit";
import { api } from "../services/api/api";
import counterReducer from "../features/counter/counterSlice";
import customersReducer from "../features/customers/store/customersSlice";
import subscriptionsReducer from "../features/subscriptions/store/subscriptionsSlice";
import uiReducer from "../features/common/store/uiSlice";
import authReducer from "../features/auth/store/authSlice";
describe("LoginIntegrationTests", () => {
afterEach(() => {
cleanup();
});
it("should render the correct initial state", function () {
render(<App />);
// it doesnt render an appbar
let navbar = screen.queryByRole("heading", {
name: /fincon admin console/i,
});
expect(navbar).not.toBeInTheDocument();
// it renders an empty email address field
const emailField = screen.getByLabelText(/email address/i);
expect(emailField).toHaveTextContent("");
// it renders an empty password password field and hides the input
const passwordField = screen.getByLabelText(/password/i);
expect(passwordField).toHaveTextContent("");
expect(passwordField).toHaveAttribute("type", "password");
// it renders a disabled login button
const loginButton = screen.getByRole("button", { name: /login/i });
emailField.focus();
expect(loginButton).toBeDisabled();
});
it("should complete a successful login flow", async function () {
render(<App />);
// it fills out the email address and password
const emailField = screen.getByLabelText(/email address/i);
const passwordField = screen.getByLabelText(/password/i);
await userEvent.type(emailField, "joe#soap.co.za");
await userEvent.type(passwordField, "blabla");
// it clicks the login button
const loginButton = screen.getByRole("button");
expect(loginButton).toHaveTextContent(/login/i);
userEvent.click(loginButton);
// it sets the loading state
expect(loginButton).toBeDisabled();
expect(loginButton).toHaveTextContent(/loading .../i);
const loadingSpinner = document.querySelector(".k-loading-mask");
expect(loadingSpinner).toBeInTheDocument();
// it removes the previous page's components
await waitFor(() => {
expect(emailField).not.toBeInTheDocument();
expect(passwordField).not.toBeInTheDocument();
expect(loginButton).not.toBeInTheDocument();
expect(loadingSpinner).not.toBeInTheDocument();
});
// it navigates to the customers page
const accountsPage = screen.getByRole("heading", { name: /accounts/i });
expect(accountsPage).toBeInTheDocument();
// it displays the appbar
const navbar = screen.getByRole("heading", {
name: /fincon admin console/i,
});
expect(navbar).toBeInTheDocument();
});
it("should present an error when invalid credentials are entered", async function () {
render(<App />);
// it fills in invalid credentials
const emailField = screen.getByLabelText(/email address/i);
const passwordField = screen.getByLabelText(/password/i);
await userEvent.type(emailField, "error#error.co.za");
await userEvent.type(passwordField, "blabla1");
// it clicks the login button
const loginButton = screen.getByRole("button");
expect(loginButton).toHaveTextContent(/login/i);
userEvent.click(loginButton);
// it sets the loading state
expect(loginButton).toBeDisabled();
expect(loginButton).toHaveTextContent(/loading .../i);
const loadingSpinner = document.querySelector(".k-loading-mask");
expect(loadingSpinner).toBeInTheDocument();
// it removes the loading spinner
await waitForElementToBeRemoved(loadingSpinner);
// it displays the error
const errors = await screen.findByText(
/the provided credentials are invalid/i
);
expect(errors).toBeInTheDocument();
// it stays on the same page
expect(screen.getByText(/log into the admin console/i)).toBeInTheDocument();
// it retains the input of the fields
expect(emailField).toHaveValue("error#error.co.za");
expect(passwordField).toHaveValue("blabla1");
});
});
Below is my redux setup for the tests:
import React from "react";
import { render as rtlRender } from "#testing-library/react";
import { configureStore } from "#reduxjs/toolkit";
import { Provider, useDispatch } from "react-redux";
import { Router } from "react-router-dom";
import { createMemoryHistory } from "history";
import { reducer, store } from "../app/store";
import { api } from "../services/api/api";
import { setupListeners } from "#reduxjs/toolkit/query";
import { renderHook } from "#testing-library/react-hooks";
import counterReducer from "../features/counter/counterSlice";
import customersReducer from "../features/customers/store/customersSlice";
import subscriptionsReducer from "../features/subscriptions/store/subscriptionsSlice";
import uiReducer from "../features/common/store/uiSlice";
import authReducer from "../features/auth/store/authSlice";
// import { useAppDispatch } from "../app/hooks";
function render(
ui,
{
preloadedState,
store = configureStore({
reducer: {
[api.reducerPath]: api.reducer,
counter: counterReducer,
customers: customersReducer,
subscriptions: subscriptionsReducer,
ui: uiReducer,
auth: authReducer,
},
preloadedState,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware),
}),
...renderOptions
} = {}
) {
setupListeners(store.dispatch);
function Wrapper({ children }) {
const history = createMemoryHistory();
return (
<Provider store={store}>
<Router history={history}>{children}</Router>
</Provider>
);
}
// function useAppDispatch() {
// return useDispatch();
// }
// type AppDispatch = typeof store.dispatch;
// const useAppDispatch = () => useDispatch<AppDispatch>();
store.dispatch(api.util.resetApiState());
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
}
export * from "#testing-library/react";
export { render };
Below is my setupTests.ts file.
import "#testing-library/jest-dom/extend-expect";
import { server } from "./mocks/server";
beforeAll(() => server.listen());
afterAll(() => server.close());
afterEach(() => {
server.resetHandlers();
});
And finally my MSW files.
handlers
import { rest } from "msw";
import { authResponse } from "./data";
import { customers } from "../utils/dummyData";
import { LoginRequest } from "../app/types/users";
import { ApiFailResponse } from "../app/types/api";
export const handlers = [
rest.post("/login", (req, res, ctx) => {
const body = req.body as LoginRequest;
if (body.emailAddress === "error#error.co.za") {
const response: ApiFailResponse = {
errors: ["The provided credentials are invalid"],
};
return res(ctx.status(400), ctx.json(response));
} else {
return res(ctx.json(authResponse));
}
}),
rest.get("/customers", (req, res, ctx) => {
return res(ctx.json(customers));
}),
];
server
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);
Any ideas?
Thanks for all your help!
You should probably also reset the api between the tests, as the api has internal state as well.
Call
afterEach(() => {
store.dispatch(api.util.resetApiState())
})
For reference, this is how RTK Query internally sets up the tests: https://github.com/reduxjs/redux-toolkit/blob/4fbd29f0032f1ebb9e2e621ab48bbff5266e312c/packages/toolkit/src/query/tests/helpers.tsx#L115-L169
This was due to a bug in my app that appears in edge cases, as #phry correctly guessed.

Firebase error: No firebase app default has been created

I am making a food delivery app using react-native and redux. I want to fetch the data from the firebase store and for that, I have written a function in the actions.js, but whenever I run the app it shows the Error
Firebase: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp() (app/no-app).
Here is the function which I am using to fetch the data
action.js
import firebase from "firebase"
export const ProductDetails = (data) => {
return {
type: "PRODUCT_ITEMS",
payload: {
data,
},
};
};
var db = firebase.firestore();
var docRef = db.collection("Products").doc("Items");
export const FetchItems = () => {
return async function (dispatch) {
return await docRef
.get()
.then((doc) => {
if (doc.exists) {
console.log("Document Data", doc.data());
dispatch(ProductDetails(doc.data));
} else {
console.log("NO such document");
}
})
.catch((error) => {
console.log(error);
});
};
};
Here is my App.js file
import React, { useState } from "react";
import { StyleSheet, Text, View, Dimensions } from "react-native";
import { NavigationContainer } from "#react-navigation/native";
import AppStack from "./components/navigation/AppStack";
import firebase from "firebase";
import {Provider} from "redux"
import store from "./store"
import { useDispatch, useSelector, Provider } from "react-redux";
import store from "./redux/store";
import AppWrapper from "./AppWrapper";
export default function App() {
const firebaseConfig = {
};
if (firebase.apps.length === 0) {
firebase.initializeApp(firebaseConfig);
}
return (
<Provider store={store}>
<NavigationContainer>
<AppStack />
</NavigationContainer>
</Provider>
);;
}
I would recommend creating a separate file firebase.js and export Firebase services from there after initialization.
firebase.js
import firebase from 'firebase/app';
import 'firebase/firestore'
const firebaseConfig = {...};
if (!firebase.apps.length) {
firebase.initializeApp(config);
}
export const auth = firebase.auth();
export const firestore = firebase.firestore()
Then import these wherever you need them:
// App.js for example
import {firestore, auth} from './firebase.js'
//var docRef = firestore.collection("Products").doc("Items");
for users with expo(v44) and firebase(v9)
firebaseUtil.js
import { initializeApp, getApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { getAuth } from "firebase/auth";
// Initialize Firebase
const firebaseConfig = {
...
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const auth = getAuth(app);
export { db, auth };
Login.js
import { auth } from "../../util/firebaseUtil";
export default function Login() {
const onSignUp = () => {
signInWithEmailAndPassword(auth, email, password);
};
return (
...
)
}
I think my error is caused by Webpack chunking(bootstrap). I did import the file with initializeApp(). My work around for who has the same problem.
Modularize the initializeApp in one file(initFire for me) and export anything(doesn't have to be app)
Import & Export before first usage of getApp()(methods like getAuth() will call it),
In this way, after Webpack it will still run first. (ES6 export {app} from "./initFire")
(This answer is grepped from my own GitHub Wiki, not plagiarizing)

How can I use Firebase Emulators to test my React App using Jest?

I'm trying to use the FirebaseEmulator to test my react components that use Firebase RealtimeDatabase and Firebase Storage.
App.tsx
import firebase from "firebase/app";
import 'firebase/storage'
import { STORAGE_ROOT } from "../../../../Work/hazy-webapp/src/model/storage/storage";
import React, {useState} from 'react'
export default ()=>{
const [url, setUrl] = useState<null | string>(null)
const storageRef = firebase
.storage()
.ref('path/file').getDownloadURL().then((v)=>{
setUrl(v);
})
return <div>
{url}
</div>
}
And here's my test:
App.test.tsx:
import {initializeTestApp, useEmulators} from '#firebase/rules-unit-testing'
import App from '../App'
useEmulators({
storage:{
host: 'localhost',
port: 9199
}
})
initializeTestApp({
storageBucket: "my-bucket",
auth: { uid: "alice" }
})
describe('test', ()=>{
it('can match snapshot', () => {
const { baseElement } = render(
<App/>
);
expect(baseElement).toMatchSnapshot();
})
})
I'm constantly getting 'Uncaught [FirebaseError: Firebase: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp()'
Am I doing this right? thank you

Resources