How to access the React Query `queryClient` outside the provider? e.g. to invalidate queries in Cypress tests - reactjs

Is it possible to access my app's React Query queryClient outside React?
In my some of my Cypress tests I fetch or mutate some data but then I'd like to invalidate all/part of the queryClient cache afterwards.
Is that possible?
I have tried importing the "same" queryClient that is used in the app, but it doesn't work.
ℹ️ I include these fetch/mutations in my tests merely to allow me to bypass convoluted steps that user's would normally take in the app which already have Cypress tests.

You can add a reference to queryClient to window, then invoke it's methods in the test.
From #АлексейМартинкевич example code,
import { QueryClient } from "react-query";
const queryClient = new QueryClient();
if (window.Cypress) { // only during testing
window.queryClient = queryClient;
}
export queryClient;
In the test
cy.window().then(win => { // get the window used by app (window in above code)
win.queryClient.invalidateQueries(...) // exact same instance as app is using
})
I believe that importing will give you a new instance, you must pass a reference to the app's active instance.
Just read your comment which says exactly this - leaving answer as a code example.

You can store query client as a separate module/global variable. demo

Related

Specifically, how does Reactjs retrieve data from firebase function triggers?

I am using express to create my firebase functions, and I understand how to create regular callable functions. I am lost however on the exact way to implement trigger functions for the background (i.e. onCreate, onDelete, onUpdate, onWrite), as well as how Reactjs in the frontend is supposed to receive the data.
The scenario I have is a generic chat system that uses react, firebase functions with express and realtime database. I am generally confused on the process of using triggers for when someone sends a message, to update another user's frontend data.
I have had a hard time finding a tutorial or documentation on the combination of these questions. Any links or a basic programmatic examples of the life cycle would be wonderful.
The parts I do understand is the way to write a trigger function:
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
.onWrite((change, context) => {
// Only edit data when it is first created.
if (change.before.exists()) {
return null;
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
// Grab the current value of what was written to the Realtime Database.
const original = change.after.val();
console.log('Uppercasing', context.params.pushId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to the Firebase Realtime Database.
// Setting an "uppercase" sibling in the Realtime Database returns a Promise.
return change.after.ref.parent.child('uppercase').set(uppercase);
});
But I don't understand how this is being called or how the data from this reaches frontend code.
Background functions cannot return anything to client. They run after a certain event i.e. onWrite() in this case. If you want to update data at /messages/{pushId}/original to other users then you'll have to use Firebase Client SDK to listen to that path:
import { getDatabase, ref, onValue} from "firebase/database";
const db = getDatabase();
const msgRef = ref(db, `/messages/${pushId}/original`);
onValue(msgRef, (snapshot) => {
const data = snapshot.val();
console.log(data)
});
You can also listen to /messages/${pushId} with onChildAdded() to get notified about any new node under that path.

session.subscribe throws error when called in onMount

<script>
import {onMount} from 'svelte';
import {session} from "$app/stores"
import {writable} from 'svelte/store';
const store = writable('some value');
let value = null
onMount(() => {
// this works
// return store.subscribe( (storeValue) => {value = storeValue}); // this works
// this throws an error:
// return session.subscribe( (sessionValue) => {value = sessionValue});
// Uncaught (in promise) Error: Function called outside component initialization
});
</script>
can someone please explain to me the problem with session.subscribe and why it keeps throwing?
if I move session.subscribe outside onMount it runs fine.
Note: this code is part of a SvelteKit Project, inside a Svelte component, not a SvelteKit page/route.
What goes wrong
It seems that you are actually experiencing intended behaviour. Under the documentation for $app/stores you will find this:
Stores are contextual — they are added to the context of your root component. This means that session and page are unique to each request on the server, rather than shared between multiple requests handled by the same server simultaneously, which is what makes it safe to include user-specific data in session.
Because of that, you must subscribe to the stores during component initialization (which happens automatically if you reference the store value, e.g. as $page, in a component) before you can use them.
When you were attempting this, you probably got a callstack that looks something like this:
Error: Function called outside component initialization
at get_current_component (index.mjs:953:15)
at getContext (index.mjs:989:12) <----------Here is the problem
at getStores (stores.js:19:17)
at Object.subscribe (stores.js:70:17)
at index.svelte:10:13
at run (index.mjs:18:12)
at Array.map (<anonymous>)
at index.mjs:1816:45
at flush (index.mjs:1075:17)
at init (index.mjs:1908:9)
We can see that Svelte attempts to call getContext when you subscribe to the session. Calling getContext outside of the component root is not allowed, which causes the subscription to fail.
I agree that this is quite unintuitive and I am not really sure why they implemented it this way.
Workaround
By the way, are you really sure you only want to subscribe to session on mount? What are you trying to do?
If you really only want to subscribe to session after component mount, you could use this workaround: Create your own store that updates whenever the session changes, then listen to that.
<script>
import { onMount } from "svelte";
import { session } from "$app/stores";
import { writable } from "svelte/store";
let mySession = writable($session);
$: $mySession = $session;
onMount(()=>{
mySession.subscribe(...whatever...);
})
</script>

How to test custom React-query hook that is based on Firebase methods?

I came across react-query-firebase which are hooks that are built on React-query for firebase.
I also found library called mock service worker https://mswjs.io/ however it is based on REST and GraphQL.
Here is an example code how I would use these hooks:
import React from "react";
import { useFirestoreDocument } from "#react-query-firebase/firestore";
import {
doc
} from "firebase/firestore";
import { firestore } from "../firebase";
function GetUser() {
const id = "pW5CizOJOpXezr5lGGshDmKdVpP3";
const ref = doc(firestore, "users", id);
const user = useFirestoreDocument(["users", id], ref);
return (
<div>
{user.isLoading && <div>Loading...</div>}
{user.data && <div>{user.data.data()?.name}</div>}
</div>
);
}
export default GetUser;
I am new to testing and I have no idea how would I have to execute this test, since I am mocking requests can I use random url anyways or does it have to be firebase related methods?
react-query-firebase library is an abstraction over Firebase that encapsulates resource paths (it's an SDK, I believe). Since the paths (URLs) are abstracted from, you have at least two options for how to mock requests issued by such libraries.
Option 1: Use explicit paths
Although the exact resource paths are hidden away, requests still reference existing absolute paths. You can observe those in the "Network" tab of your browser or by enabling a simple request introspection with msw:
// my.test.js
import { setupServer } from 'msw/node'
const server = setupServer(/* no handlers */)
beforeAll(() => server.listen())
afterAll(() => server.close())
Since we're using setupServer with no handlers, all requests that happen in my.test.js will be printed as warnings to stderr. You can observe those warnings to see what resource paths your SDK requests.
The benefit of using an SDK is that it guarantees you a certain resource path structure. Most likely, you will be able to replicate that structure in your mocks:
// src/mocks.js
import { rest } from 'msw'
export const handlers = [
rest.get('https://some-resource.:checksum.firebase.app/path', (req, res, ctx) => res(ctx.text('hello)))
]
Utilize dynamic path segments like :checksum to match a broader range of paths.
The downside of this approach is that your mock definition becomes dependent on the SDK internal details (resource paths). Any updates to the SDK may break your mocks, since they may update the path structure internally without denoting it as a breaking change (abstracted paths are not public API).
Option 2: Spy on the SDK
Alternatively, you can spy on the SDK you're using. An example of such a spy would be using jest.spyOn on the react-query-firebase directly:
// my.test.js
import * as reactQueryFirebase from 'react-query-firebase'
it('creates a user', () => {
jest.spyOn(reactQueryFirebase, 'SOME_METHOD_NAME')
.mockReturnValue(/* mock */)
})
The downside of this method is that you're stubbing the functions from a third-party library, which means your code under test never calls those functions. This decreases the reliability of such a test, and you should, generally, avoid resorting to this approach.
I do recommend you research Firebase, since major SDK providers often have guidelines on mocking their API. Often such a guidance would include using a dedicated third-party package authored by the SDK provider that allows mocking their internal resources.

How do I correct official React.js testing recipe to work for React/Jest/Fetch asynchronous testing?

I am trying to learn the simplest way to mock fetch with jest. I am trying to start with the official React docs recipe for fetch but it doesnt actually work without some modification.
My aim is to use Jest and (only) native React to wait for component rendering to complete with useEffect[].fetch() call before running assertions and testing initialisation worked.
I have imported the Data fetching recipe from official docs:
https://reactjs.org/docs/testing-recipes.html#data-fetching
into codesandbox here
https://codesandbox.io/s/jest-data-fetching-ov8og
Result: test fail
expect(received).toContain(expected) // indexOf
Expected substring: "123, Charming Avenue"
Received string: "loading..."
Possible cause?: I suspect its failing because global.fetch is not being used in the component which appears to be using real fetch hence is stuck "loading".
UPDATE
I have managed to make the test work through trial and error. Changed the call to fetch in the actual component to global.fetch() to match the test. This is not desirable for working code to refer to global prefix everywhere fetch is used and is also not in the example code.
e.g.
export default function User(props) {
const [user, setUser] = useState(null);
async function fetchUserData(id) {
// this doesnt point to the correct mock function
// const response = await fetch("/" + id);
// this fixes the test by pointing to the correct mock function
const response = await global.fetch("/" + id);
const json = await response.json();
setUser(json);
}
...
any help or advice much appreciated.

Sentry for micro frontends

Is there any possibility to initialise Sentry twice on a page? Use case would be error tracking for parts of the app that are inserted as microfrontend.
So errors happen in this part of the app should be send to the teams own sentry project. I also wonder if there is any way filter the errors so only the ones that are relevant for the microfrontend are send and others are filtered out. Could we use reacts error boundaries for that?
Looks like there is a way to initialize something called Hub with a second dsn like this:
import {BrowserClient, Hub} from '#sentry/browser';
const client = new BrowserClient({
dsn: 'micorFrontEndSntryInstance'
});
const hub = new Hub(client)
this hub can passed to an ErrorBoundary wrapping your component.
In every componentDidCatch we can then send the error to the micro frontends sentry:
componentDidCatch(error, errorInfo) {
this.props.hub.run(currentHub => {
currentHub.withScope((scope) => {
scope.setExtras(errorInfo);
currentHub.captureException(error);
});
})
}
All the code is from this example implementation.

Resources