Mocking apollo link state - reactjs

I am trying to mock a query #client and I am not getting.
I mocked the query from graphql server correctly and it's working.
import React from 'react';
import renderer from 'react-test-renderer';
import wait from 'waait';
import ExchangeRates from './ExchangeRates';
import { MockedProvider } from 'react-apollo/test-utils';
import { sucessMockrates, errorMockrates } from '../../mocks/exchangeRatesMock';
describe('ExchangeRates', () => {
it('should render rate', async () => {
const component = renderer.create(
<MockedProvider mocks={[sucessMockrates]} addTypename={false}>
<ExchangeRates />
</MockedProvider>
);
await wait(0);
const p = component.root.findByType('p');
expect(p.children).toContain('AED: 3.67');
});
it('should render loading state initially', () => {
const component = renderer.create(
<MockedProvider mocks={[]}>
<ExchangeRates />
</MockedProvider>
);
const tree = component.toJSON();
expect(tree.children).toContain('Loading...');
});
it('should show error UI', async () => {
const component = renderer.create(
<MockedProvider mocks={[errorMockrates]} addTypename={false}>
<ExchangeRates />
</MockedProvider>
);
await wait(0);
const tree = component.toJSON();
expect(tree.children).toContain('Error!');
});
});
I am using the graphql server link from apollo tutorial
But when I tried to test the apollo query with local state I got an error.
My query:
import gql from 'graphql-tag';
export default gql`
query {
allocations #client {
list
}
}
`;
and my apollo client setup:
const cache = new InMemoryCache();
const defaultState = {
allocations: {
__typename: 'Allocations',
list: [],
},
};
const listQuery = gql`
query getAllocations {
allocations #client {
list
}
}
`;
const stateLink = withClientState({
cache,
defaults: defaultState,
resolvers: {
addAllocation: (
_,
{ userName },
{ cache }
) => {
const previousState = cache.readQuery({ query: listQuery });
const { list } = previousState.allocations;
const data = {
...previousState,
allocations: {
...previousState.allocations,
list: [
...list,
{
userName
},
],
},
};
cache.writeQuery({ query: listQuery, data });
return data.allocations;
},
},
},
});
const client = new ApolloClient({
link: ApolloLink.from([
stateLink,
new HttpLink({
uri: 'https://w5xlvm3vzz.lp.gql.zone/graphql',
}),
]),
cache,
});
My test with apollo local state:
import React from 'react';
import renderer from 'react-test-renderer';
import AllocationListPage from './AllocationListPage';
import { MockedProvider } from 'react-apollo/test-utils';
import { sucessMockAllocations } from '../../../mocks/allocationListMock';
describe('AllocationListPage', () => {
it('should render list of allocations', () => {
renderer.create(
<MockedProvider mocks={[sucessMockAllocations]} addTypename={false}>
<AllocationListPage />
</MockedProvider>
);
});
});
The error I got: TypeError:
Cannot destructure property list of 'undefined' or 'null'.
I need to mock the initial state of apollo local state, and I don't know how.
Thanks in advance.

I got setup my apollo link state with this component:
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { ApolloProvider } from 'react-apollo';
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
import { ApolloClient } from 'apollo-client';
import { stateLink, cache } from '../graphql/stateLink';
import { ApolloLink } from 'apollo-link';
import { SchemaLink } from 'apollo-link-schema';
const setupClient = mocks => {
const typeDefs = `
type Query {
test: String!
}
`;
const schema = makeExecutableSchema({ typeDefs });
addMockFunctionsToSchema({
schema,
mocks,
preserveResolvers: false,
});
return new ApolloClient({
cache,
link: ApolloLink.from([stateLink, new SchemaLink({ schema })]),
});
};
class ApolloLinkStateSetup extends PureComponent {
render() {
return (
<ApolloProvider client={setupClient(this.props.mocks)}>
{this.props.children}
</ApolloProvider>
);
}
}
ApolloLinkStateSetup.defaultProps = {
mocks: {},
};
ApolloLinkStateSetup.propTypes = {
children: PropTypes.object.isRequired,
mocks: PropTypes.object,
};
export default ApolloLinkStateSetup;
You can mock the graphql queries with makeExecutableSchema and addMockFunctionsToSchema from graphql-tools. This mock can be useful to create the front-end side without the back-end side.

Related

getStaticProps returns an empty object

I'm using nextJS V9.5.5 with wp-graphql and apolloClient to get data from WordPress. Everything works fine, but when I try to return context (in the purpose of getting query) from getStaticProps() like it's described in docs, it returns an empty object.
Custom App:
import React from "react";
import getConfig from "next/config";
import LayoutOuter from "../components/LayoutOuter";
import "bootstrap/dist/css/bootstrap.css";
import { ApolloProvider } from "#apollo/client";
import { useApollo } from "../lib/apolloClient";
import { initializeApollo } from "../lib/apolloClient";
import { gql } from "#apollo/client";
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();
const { DOMAIN } = publicRuntimeConfig;
function CustomApp({ pageProps, Component, props }) {
const apolloClient = useApollo(pageProps.initialApolloState);
return (
<ApolloProvider client={apolloClient}>
{console.log("_app", props)}
<LayoutOuter>
<Component {...pageProps} />
</LayoutOuter>
</ApolloProvider>
);
}
CustomApp.getInitialProps = async (ctx) => {
const apolloClient = initializeApollo();
await apolloClient.query({
query: gql`
{
// my graphql query here
}
`,
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
ctx: JSON.stringify(ctx),
},
};
};
export default CustomApp;
One of the page:
import React, { Component, useEffect, useState } from "react";
import getConfig from "next/config";
import { NextSeo } from "next-seo";
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();
const { DOMAIN, SITENAME } = publicRuntimeConfig;
import { initializeApollo } from "../lib/apolloClient";
import { gql } from "#apollo/client";
import "./services.module.scss";
const Home = (props) => {
let currentPage = Object.values(props.initialApolloState.ROOT_QUERY)[1];
const {
title,
metadesc,
metaRobotsNoindex,
metaRobotsNofollow,
metaRobotsAdv,
opengraphTitle,
opengraphDescription,
opengraphImage,
twitterTitle,
twitterDescription,
twitterImage,
} = currentPage.seo;
return (
<>
{console.log("project", props)}
<NextSeo
noindex={metaRobotsNoindex}
nofollow={metaRobotsNofollow}
title={title != "" ? title : `${props.data.pagetitle} - ${SITENAME}`}
description={metadesc}
canonical={DOMAIN}
openGraph={{
url: DOMAIN,
title:
opengraphTitle != ""
? opengraphTitle
: `${props.data.pagetitle} - Garrison Collection`,
description: opengraphDescription,
images: [
{
url: opengraphImage,
width: 800,
height: 600,
alt: { SITENAME },
},
],
site_name: { SITENAME },
}}
/>
<p>works</p>
</>
);
};
export async function getStaticProps(context) {
const apolloClient = initializeApollo();
await apolloClient.query({
query: gql`
{
project(id: "ca-souls", idType: SLUG) {
seo {
canonical
metaDesc
metaKeywords
metaRobotsNofollow
metaRobotsNoindex
opengraphAuthor
opengraphDescription
opengraphModifiedTime
opengraphPublishedTime
opengraphPublisher
opengraphSiteName
opengraphTitle
opengraphType
opengraphUrl
title
twitterDescription
twitterTitle
}
}
}
`,
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
context: JSON.stringify(context) || null,
},
revalidate: 1,
};
}
export default Home;
Here is the log result:
How could I get context.query?
The context parameter includes previewData contains the preview data set by setPreviewData. This means including function, therefore, unable to serealize. Take values out from context.params.

How to get client from wrapper in next.js getServerSideProps()?

I'm trying to use next.js with apollo graphql for server-side rendering. I know that to do that i need to run the necessary queries inside getServerSideProps(), which then will pass the props into the main component, where i will be able to render the results.
I created a provider to make sure all components in the tree get the same client object.
import withApollo from "next-with-apollo";
import { ApolloClient, InMemoryCache } from "#apollo/client";
import { ApolloProvider } from "#apollo/react-hooks";
export default withApollo(
() => {
return new ApolloClient({
ssrMode: true,
uri: "https://my.api/graphql",
cache: new InMemoryCache()
});
},
{
render: ({ Page, props }) => {
return (
<ApolloProvider client={props.apollo}>
<Page {...props} />
</ApolloProvider>
);
}
}
);
but how can i get this client inside the getServerSideProps() function if it's not being wrapped by withApollo()?
import gql from "graphql-tag";
import { useQuery } from "#apollo/react-hooks";
import { ApolloClient } from "#apollo/client";
import withApollo from "next-with-apollo";
const MY_QUERY = gql`
query MyQuery {
myQuery {
name
}
}
`;
function MyComponent(props) {
return (
<div className="landing-section__topcontainer ph-lg-8 ph-3">
<div className="overflow-list-container">
<div className="landing-horizontal-list">
{props.res.map(q => {
return (
<div className="tag-tile__title">{q.name}</div>
);
})}
</div>
</div>
</div>
);
}
export async function getServerSideProps() {
// Fetch data from external API
const apolloClient = getApolloClient();
const { data } = await apolloClient.query({
query: MY_QUERY
});
const res = data.myQuery;
return { props: { res } };
}
export default withApollo(MyComponent);

TypeError: ref.collection is not a function using React-Redux-Firebase/React-Testing-Library

I have been having a ton of trouble trying to render a react-redux-firebase connected component using react-testing-library. I keep getting hit with a [TypeError: ref.collection is not a function] error that crashes my test as soon as I try to run it.
The component:
#firestoreConnect((props, {getState}) => {
const {auth} = getState();
const {clientId} = auth.data;
return [
{
collection: 'clients',
doc: clientId,
subcollections: [{collection: 'users'}]
},
{
collection: 'clients',
doc: clientId,
subcollections: [{collection: 'scripts'}]
},
{
collection: 'clients',
doc: clientId,
subcollections: [{collection: 'teams'}]
},
{
collection: 'clients',
doc: clientId,
subcollections: [{collection: 'lists'}],
where: [
['status', '==', 'complete'],
['archived', '==', false]
]
}
];
})
#connect(({auth, profile, ggFirestore}, {params}) => {
const {clientRef, displayName, legacyClientId, permissions} = auth.data;
return {
users: ggFirestore.getIn(['users']),
lists: ggFirestore.getIn(['lists']),
scripts: ggFirestore.getIn(['scripts']),
teams: ggFirestore.getIn(['teams']),
profile,
clientRef,
displayName,
legacyClientId,
permissions,
params
};
})
export default class CampaignForm extends Component {
constructor(props) {
super(props);
const campaignTypes = {
'1to1sms': [
VisibilityStep,
TemplatesStep,
ListsStep,
OwnerStep,
NameStep,
SummaryStep
],
'1to1email': [
NameStep,
TemplatesStep,
ListsStep,
SendEmailsStep,
SuccessStep
]
};
this.state = {
step: 0,
contactTypeSteps: campaignTypes[props.params.contactType],
campaignId: '',
draftCampaignId: '',
type: 'organizing',
isProcessing: false,
confirmSummary: false,
contactType: props.params.contactType
};
}
...
render() {
const {
step,
success,
error,
contactTypeSteps,
campaignId,
draftCampaignId,
isProcessing,
confirmSummary,
contactType
} = this.state;
const {params, lists, users, teams, scripts, profile} = this.props;
const isLastStep =
contactTypeSteps.length === step + 1 ||
(contactType === '1to1email' && step === 3);
const StepComponent = contactTypeSteps[step];
if (profile && profile.role === 'volunteer') {
return <PermissionDenied />;
}
if (users && lists && scripts && teams) {
return (
<div className="top-spacer" date-testid="_campaign-form">
<SuccessMessage
message={success}
handleDismiss={() => {
this.setState({success: null});
}}
/>
<ErrorMessage
error={error}
handleDismiss={() => {
this.setState({error: null});
}}
/>
{confirmSummary ? (
<SuccessStep
campaignId={campaignId ? campaignId : draftCampaignId}
campaignType={params.contactType}
{...this.state}
{...this.props}
isOrganizer={profile && profile.role === 'organizer'}
/>
) : (
<StepComponent
handleNext={
isLastStep ? ::this.handleFinalSubmit : ::this.handleNextClick
}
handlePrev={::this.handleBackClick}
handleChange={::this.handleChange}
contactType={params.contactType}
isLastStep={isLastStep}
isProcessing={isProcessing}
{...this.props}
{...this.state}
isOrganizer={profile && profile.role === 'organizer'}
/>
)}
</div>
);
}
return <Loading />;
}
}
My test wrapper:
import '#testing-library/jest-dom/extend-expect';
import 'firebase/firestore';
import { render } from '#testing-library/react';
import firebase from 'firebase';
import { createMemoryHistory } from 'history';
import React from 'react';
import { Provider } from 'react-redux';
import { reactReduxFirebase } from 'react-redux-firebase';
import { MemoryRouter } from 'react-router-dom';
import { compose, createStore } from 'redux';
import { reduxFirestore } from 'redux-firestore';
import rootReducer from '../ducks';
jest.mock('../store/fbTestConfig', () => ({
firebase: {
firestore: jest.fn(() => ({})),
initializeApp: jest.fn(() => {})
}
}));
const mockStore = compose(
reactReduxFirebase(firebase, {}),
reduxFirestore(firebase)
)(createStore);
export function renderWithRedux(
Component,
{
initialState,
store = mockStore(rootReducer, initialState),
route = '/',
history = createMemoryHistory({initialEntries: [route]})
} = {}
) {
return {
...render(
<Provider store={store}>
<MemoryRouter initialEntries={[route]}>{Component}</MemoryRouter>
</Provider>,
{}
),
store,
history
};
}
My test:
import '#testing-library/jest-dom/extend-expect';
import { cleanup } from '#testing-library/react';
import React from 'react';
import CampaignForm from '../../components/Campaigns/CampaignForm';
import { renderWithRedux } from '../renderWithRedux';
beforeEach(cleanup);
test('CampaignForm renders', () => {
const {debug} = renderWithRedux(<CampaignForm />, {
route: 'organize/1to1sms'
});
debug();
});
I tried console logging the component and it looks like it is wrapped properly by firestoreConnect, but the ref key is always set to null. I have tried looking everywhere for some kind of answer but no luck. Any help would be GREATLY appreciated!
So I finally figured it out! The component was expecting props that I did not seed properly when setting up my test.
In my component I had these lines:
const {auth} = getState();
const {clientId} = auth.data;
Where auth is a piece of my application state. The initial state for this was an empty Map, so when I tried to pull clientId from there and use it to get my collections, it threw an error.
The solution was just using my mock store to dispatch the appropriate action to populate the auth Map. Worked like a charm.

NextJS: How to add screen loading for production build?

I want add screen loading in next js project. And I tried to do that with the Router component in next/router.
This is my _app.js in next.js project:
import {CookiesProvider} from 'react-cookie';
import App from 'next/app'
import React from 'react'
import {Provider} from 'react-redux'
import withRedux from 'next-redux-wrapper'
import withReduxSaga from 'next-redux-saga'
import createStore from '../src/redux/store'
import Router from "next/router";
import {Loaded, Loading} from "../src/util/Utils";
class MyApp extends App {
static async getInitialProps({Component, ctx}) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps({ctx})
}
return {pageProps}
}
render() {
Router.onRouteChangeStart = () => {
Loading()
};
Router.onRouteChangeComplete = () => {
Loaded()
};
Router.onRouteChangeError = () => {
Loaded()
};
const {Component, pageProps, store} = this.props;
return (
<CookiesProvider>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</CookiesProvider>
)
}
}
export default withRedux(createStore)(withReduxSaga(MyApp))
This is Loaded() and Loading() functions:
export const Loaded = () => {
setTimeout(() => {
let loading = 'has-loading';
document.body.classList.remove(loading);
}, 100);
};
export const Loading = () => {
let loading = 'has-loading';
document.body.classList.add(loading);
};
The code works well when the project is under development mode. But when the project is built, the loading won't disappear.
Do you know solution of this issue or are you suggesting another solution?
Using apollo client and react hooks you could do as follow.
Example:
import { useQuery } from '#apollo/react-hooks';
import gql from 'graphql-tag';
import { withApollo } from '../lib/apollo';
import UserCard from '../components/UserCard';
export const USER_INFO_QUERY = gql`
query getUser ($login: String!) {
user(login: $login) {
name
bio
avatarUrl
url
}
}
`;
const Index = () => {
const { query } = useRouter();
const { login = 'default' } = query;
const { loading, error, data } = useQuery(USER_INFO_QUERY, {
variables: { login },
});
if (loading) return 'Loading...'; // Loading component
if (error) return `Error! ${error.message}`; // Error component
const { user } = data;
return (
<UserCard
float
href={user.url}
headerImg="example.jpg"
avatarImg={user.avatarUrl}
name={user.name}
bio={user.bio}
/>
);
};
export default withApollo({ ssr: true })(Index);
More info here: https://github.com/zeit/next.js/tree/canary/examples/with-apollo
I added the following codes to a wrapper component and the problem was resolved.
componentDidMount() {
Loaded();
}
componentWillUnmount() {
Loading();
}

apollo client: local state being overwritten by query?

I have an app that uses:
react-apollo
apollo-link-state
apollo-cache-persist
apollo-link-error
I ran into a weird issue when I was trying to add a pollInterval to a query. The pollInterval causes an overWrite of the local cache state back to the defaults. I am not sure if this is caused directly by the pollInterval or not, but I have been troubleshooting the issue for some time with no luck.
I made a reproduction of the issue on Github here (https://github.com/deltaskelta/apollo-link-iss-573) with instructions on how to see what is happening. If anyone has any idea what is going on I would really appreciate it!
Thanks
EDIT: the full code that is causing the error is posted below
import { ApolloClient } from "apollo-client";
import { ApolloLink } from "apollo-link";
import { ApolloProvider, graphql } from "react-apollo";
import { CachePersistor } from "apollo-cache-persist";
import { InMemoryCache } from "apollo-cache-inmemory";
import { onError } from "apollo-link-error";
import { withClientState } from "apollo-link-state";
import PropTypes from "prop-types";
import React, { Component } from "react";
import gql from "graphql-tag";
export const resolvers = {
Mutation: {
setErrors: (_, { messages }, { cache }) => {
const data = {
errors: {
__typename: "Errors",
messages
}
};
cache.writeData({ data });
return null;
}
}
};
// create the cache
const cache = new InMemoryCache();
const persistor = new CachePersistor({
cache,
storage: window.localStorage,
debug: true
});
// link to the local state cache
const stateLink = withClientState({
cache,
resolvers,
defaults: {
errors: {
__typename: "Errors",
messages: []
}
}
});
// link to intercept graphql errors and write them to the cache, this is almost like a
// middleware
const errorLink = onError(({ graphQLErrors, networkError }) => {
var messages = []; // make a place to push all errors to
// go through the network errors and graphql errors, push them to the errors array and
// then write those errors to the cache
if (graphQLErrors) {
graphQLErrors.forEach(({ message, path }) => {
messages.push(`Error: msg: ${message}, path: ${path}`);
});
}
if (networkError) {
messages.push(`Error: network: ${networkError}`);
}
const data = {
errors: {
__typename: "Errors",
messages
}
};
cache.writeData({ data });
});
// put both links in the frontend apollo client
const client = new ApolloClient({
cache,
link: ApolloLink.from([stateLink, errorLink]),
connectToDevTools: true
});
client.onResetStore(stateLink.writeDefaults);
class Persistor extends React.Component {
static propTypes = {
children: PropTypes.object,
persistor: PropTypes.object
};
componentDidMount() {
this.props.persistor.restore().then(() => {
console.log("restored");
});
}
render() {
return this.props.children;
}
}
class App extends Component {
render() {
return (
<ApolloProvider client={client}>
<Persistor persistor={persistor}>
<Errors />
</Persistor>
</ApolloProvider>
);
}
}
const RootComponent = ({ errors }) => {
return (
<div>
Hello World
{errors.map(e => <div>{e}</div>)}
</div>
);
};
RootComponent.propTypes = {
errors: PropTypes.array
};
const Errors = graphql(
gql`
query GetErrors {
errors #client {
messages
}
}
`,
{
props: ({ data }) => ({ errors: data.errors.messages }),
options: {
pollInterval: 1000 * 10
}
}
)(RootComponent);
export default App;
// TODO:
//
// - cause an error that is written to the cache
// - make a query to poll the error

Resources