diff --git a/.changeset/hot-cycles-notice.md b/.changeset/hot-cycles-notice.md
new file mode 100644
index 00000000000..0ad7bc4d543
--- /dev/null
+++ b/.changeset/hot-cycles-notice.md
@@ -0,0 +1,5 @@
+---
+"@apollo/client": major
+---
+
+Remove the deprecated `graphql`, `withQuery`, `withMutation`, `withSubscription`, and `withApollo` hoc components. Use the provided React hooks instead.
diff --git a/.changeset/young-turtles-explode.md b/.changeset/young-turtles-explode.md
new file mode 100644
index 00000000000..cf98093ce52
--- /dev/null
+++ b/.changeset/young-turtles-explode.md
@@ -0,0 +1,5 @@
+---
+"@apollo/client": major
+---
+
+Remove the deprecated `Query`, `Mutation`, and `Subscription` components. Use the provided React hooks instead.
diff --git a/.size-limit.cjs b/.size-limit.cjs
index a91cfa60a04..835fd3631c5 100644
--- a/.size-limit.cjs
+++ b/.size-limit.cjs
@@ -45,7 +45,6 @@ const checks = [
"@wry/equality",
"@wry/trie",
"graphql-tag",
- "hoist-non-react-statics",
"optimism",
"prop-types",
"response-iterator",
diff --git a/.size-limits.json b/.size-limits.json
index c7b4947027f..7a903b40f8b 100644
--- a/.size-limits.json
+++ b/.size-limits.json
@@ -1,4 +1,4 @@
{
- "dist/apollo-client.min.cjs": 41615,
- "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 34349
+ "dist/apollo-client.min.cjs": 41617,
+ "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 34350
}
diff --git a/config/entryPoints.js b/config/entryPoints.js
index e1778f4cb27..6098234e251 100644
--- a/config/entryPoints.js
+++ b/config/entryPoints.js
@@ -19,9 +19,7 @@ const entryPoints = [
{ dirs: ["link", "ws"] },
{ dirs: ["masking"] },
{ dirs: ["react"] },
- { dirs: ["react", "components"] },
{ dirs: ["react", "context"] },
- { dirs: ["react", "hoc"] },
{ dirs: ["react", "hooks"] },
{ dirs: ["react", "internal"] },
{ dirs: ["react", "parser"] },
diff --git a/config/jest.config.js b/config/jest.config.js
index 977c2e8e80a..1e0d714b4ac 100644
--- a/config/jest.config.js
+++ b/config/jest.config.js
@@ -29,15 +29,7 @@ const defaults = {
const ignoreTSFiles = ".ts$";
const ignoreTSXFiles = ".tsx$";
-const react19TestFileIgnoreList = [
- ignoreTSFiles,
- // The HOCs and Render Prop Components have been deprecated since March 2020,
- // and to test them we would need to rewrite a lot of our test suites.
- // We will not support them any more for React 19.
- // They will probably work, but we make no more guarantees.
- "src/react/hoc/.*",
- "src/react/components/.*",
-];
+const react19TestFileIgnoreList = [ignoreTSFiles];
const react17TestFileIgnoreList = [
ignoreTSFiles,
diff --git a/docs/source/api/react/components.mdx b/docs/source/api/react/components.mdx
deleted file mode 100644
index 3a2ae27104c..00000000000
--- a/docs/source/api/react/components.mdx
+++ /dev/null
@@ -1,63 +0,0 @@
----
-title: Components
-description: Deprecated React Apollo render prop component API
-api_doc:
- - "@apollo/client!QueryFunctionOptions:interface"
- - "@apollo/client!QueryResult:interface"
- - "@apollo/client!MutationFunctionOptions:interface"
- - "@apollo/client!MutationResult:interface"
- - "@apollo/client!SubscriptionComponentOptions:interface"
- - "@apollo/client!SubscriptionResult:interface"
----
-
-import { PropertySignatureTable } from '../../../shared/ApiDoc';
-
-> **Note:** Official support for React Apollo render prop components ended in March 2020. This library is still included in the `@apollo/client` package, but it no longer receives feature updates or bug fixes.
-
-## Installation
-
-The render prop library is included in the core `@apollo/client` package:
-
-```
-npm install @apollo/client
-```
-
-You then import the library's symbols from `@apollo/client/react/components`.
-
-## `Query`
-
-### Props
-
-The `Query` component accepts the following props. `query` is **required**.
-
-
-
-### Render prop function
-
-The render prop function that you pass to the `children` prop of `Query` is called with an object (`QueryResult`) that has the following properties. This object contains your query result, plus some helpful functions for refetching, dynamic polling, and pagination.
-
-
-
-## `Mutation`
-
-The Mutation component accepts the following props. Only `mutation` is **required**.
-
-
-
-### Render prop function
-
-The render prop function that you pass to the `children` prop of `Mutation` is called with the `mutate` function and an object with the mutation result. The `mutate` function is how you trigger the mutation from your UI. The object contains your mutation result, plus loading and error state.
-
-
-
-## `Subscription`
-
-### Props
-
-The Subscription component accepts the following props. Only `subscription` is **required**.
-
-
-
-### Render prop function
-
-
diff --git a/docs/source/api/react/hoc.mdx b/docs/source/api/react/hoc.mdx
deleted file mode 100644
index 157ae88d2fb..00000000000
--- a/docs/source/api/react/hoc.mdx
+++ /dev/null
@@ -1,1088 +0,0 @@
----
-title: HOC
-description: Deprecated React Apollo HOC API
----
-
-> **Note:** Official support for React Apollo higher order components ended in March 2020. This library is still included in the `@apollo/client` package, but it no longer receives feature updates or bug fixes.
-
-## Installation
-
-The HOC library is included in the core `@apollo/client` package:
-
-```
-npm install @apollo/client
-```
-
-You then import the library's symbols from `@apollo/client/react/hoc`.
-
-## `graphql(query, [config])(component)`
-
-```js
-import { graphql } from '@apollo/client/react/hoc';
-```
-
-The `graphql()` function is the core of Apollo's HOC API. Use this function to create higher-order components that can execute queries and update reactively based on the data in your Apollo store.
-
-The `graphql()` function returns a function that "enhances" any component with reactive GraphQL capabilities. This follows the React [higher-order component](https://reactjs.org/docs/higher-order-components.html) pattern that's also used by [`react-redux`’s `connect`](https://github.com/reduxjs/react-redux/blob/master/docs/api/connect.md) function.
-
-The `graphql()` function can only provide access to your GraphQL data if there is an [` `](./hooks/#the-apolloprovider-component) component higher up in your tree to provide an [`ApolloClient`](../core/ApolloClient/) instance that's used to fetch your data.
-
-The behavior of your component enhanced with the `graphql()` function will be different depending on if your GraphQL operation is a [query](../../data/queries/), a [mutation](../../data/mutations/), or a [subscription](../../data/subscriptions/). See the appropriate API documentation for more information about the functionality and available options for each type.
-
-### Examples
-
-You can use the `graphql()` function like this:
-
-```js
-function TodoApp({ data: { todos } }) {
- return (
-
- {todos.map(({ id, text }) => (
- {text}
- ))}
-
- );
-}
-
-export default graphql(gql`
- query TodoAppQuery {
- todos {
- id
- text
- }
- }
-`)(TodoApp);
-```
-
-You can also define an intermediate function and hook up your component with the `graphql()` function like this:
-
-```js
-// Create our enhancer function.
-const withTodoAppQuery = graphql(gql`query TodoAppQuery { ... }`);
-
-// Enhance our component.
-const TodoAppWithData = withTodoAppQuery(TodoApp);
-
-// Export the enhanced component.
-export default TodoAppWithData;
-```
-
-### The `config` object
-
-Before we look into the specific behaviors of each operation, let's look at the `config` object. The `config` object is the second argument you pass into the `graphql()` function, after your GraphQL document. The config is optional and allows you to add some custom behavior to your higher order component.
-
-```js
-export default graphql(
- gql`query MyQuery { ... }`,
- config, // <- The `config` object.
-)(MyComponent);
-```
-
-Lets go through all of the properties that may live on your `config` object.
-
-#### `config.options`
-
-`config.options` is an object or a function that allows you to define the specific behavior your component should use in handling your GraphQL data.
-
-The specific options available for configuration depend on the operation you pass as the first argument to `graphql()`. There are options specific to [queries](../../data/queries/) and [mutations](../../data/mutations/).
-
-You can define `config.options` as a plain object, or you can compute your options from a function that takes the component’s props as an argument.
-
-##### Example
-
-```js
-export default graphql(gql`query MyQuery { ... }`, {
- options: {
- // Options go here.
- },
-})(MyComponent);
-```
-
-```js
-export default graphql(gql`query MyQuery { ... }`, {
- options: props => ({
- // Options are computed from `props` here.
- }),
-})(MyComponent);
-```
-
-#### `config.props`
-
-The `config.props` property allows you to define a map function that takes the `props` (and optionally `lastProps`) added by the `graphql()` function ([`props.data`](#propsdata) for queries and [`props.mutate`](#propsmutate) for mutations) and allows you to compute a new `props` (and optionally `lastProps`) object that will be provided to the component that `graphql()` is wrapping.
-
-The function you define behaves almost exactly like [`mapProps` from Recompose](https://github.com/acdlite/recompose/blob/2e71fdf4270cc8022a6574aaf00731bfc25dcae6/docs/API.md#mapprops) providing the same benefits without the need for another library.
-
-`config.props` is most useful when you want to abstract away complex function calls into a simple prop that you can pass down to your component.
-
-Another benefit of `config.props` is that it also allows you to decouple your pure UI components from your GraphQL and Apollo concerns. You can write your pure UI components in one file and then keep the logic required for them to interact with the store in a completely different place in your project. You can accomplish this by your pure UI components only asking for the props needed to render and `config.props` can contain the logic to provide exactly the props your pure component needs from the data provided by your GraphQL API.
-
-##### Example
-
-This example uses [`props.data.fetchMore`](#datafetchmoreoptions).
-
-```js
-export default graphql(gql`query MyQuery { ... }`, {
- props: ({ data: { fetchMore } }) => ({
- onLoadMore: () => {
- fetchMore({ ... });
- },
- }),
-})(MyComponent);
-
-function MyComponent({ onLoadMore }) {
- return (
-
- Load More!
-
- );
-}
-```
-
-To access props that are not added by the `graphql()` function, use the `ownProps` keyword. For example:
-
-```js
-export default graphql(gql`query MyQuery { ... }`, {
- props: ({ data: { liveImage }, ownProps: { loadingImage } }) => ({
- image: liveImage || loadingImage,
- }),
-})(MyComponent);
-```
-
-To access `lastProps`, use the second argument of `config.props`. For example:
-
-```js
-export default graphql(gql`query MyQuery { ... }`, {
- props: ({ data: { liveImage } }, lastProps) => ({
- image: liveImage,
- lastImage: lastProps.data.liveImage,
- }),
-})(MyComponent);
-```
-
-#### `config.skip`
-
-If `config.skip` is `true`, then all of the React Apollo code is skipped _entirely_. Your component behaves as if the `graphql()` function isn't there at all.
-
-You can also pass a function to `config.skip`. If you do, the function takes your component's props and should return a boolean. If the function returns `true`, then the skip behavior goes into effect.
-
-`config.skip` is especially useful if you want to use a different query based on some prop. You can see this in an example below.
-
-##### Example
-
-```js
-export default graphql(gql`query MyQuery { ... }`, {
- skip: props => !!props.skip,
-})(MyComponent);
-```
-
-The following example uses the [`compose`](https://github.com/acdlite/recompose/blob/master/docs/API.md#compose) function to use multiple `graphql()` enhancers at once.
-
-```js
-export default compose(
- graphql(gql`query MyQuery1 { ... }`, { skip: props => !props.useQuery1 }),
- graphql(gql`query MyQuery2 { ... }`, { skip: props => props.useQuery1 }),
-)(MyComponent);
-
-function MyComponent({ data }) {
- // The data may be from `MyQuery1` or `MyQuery2` depending on the value
- // of the prop `useQuery1`.
- console.log(data);
-}
-```
-
-#### `config.name`
-
-This property allows you to configure the name of the prop that gets passed down to your component. By default, if the GraphQL document you pass into `graphql()` is a query, then your prop is named [`data`](#propsdata). If you pass a mutation, then your prop will be named [`mutate`](#propsmutate). These default names collide when you use multiple queries or mutations with the same component. To avoid collisions, use `config.name` to specify a different name.
-
-##### Example
-
-This example uses the [`compose`](https://github.com/acdlite/recompose/blob/master/docs/API.md#compose) function to use multiple `graphql()` HOCs together.
-
-```js
-export default compose(
- graphql(gql`mutation CreateTodoMutation (...) { ... }`, { name: 'createTodo' }),
- graphql(gql`mutation UpdateTodoMutation (...) { ... }`, { name: 'updateTodo' }),
- graphql(gql`mutation DeleteTodoMutation (...) { ... }`, { name: 'deleteTodo' }),
-)(MyComponent);
-
-function MyComponent(props) {
- // Instead of the default prop name, `mutate`,
- // we have three different prop names.
- console.log(props.createTodo);
- console.log(props.updateTodo);
- console.log(props.deleteTodo);
-
- return null;
-}
-```
-
-#### `config.withRef`
-
-By setting `config.withRef` to `true`, you can get the instance of your wrapped component from your higher-order GraphQL component using a `getWrappedInstance` method available on the instance of your higher-order GraphQL component.
-
-You might want to set this to `true` when you want to call functions or access properties that are defined on your wrapped component’s class instance.
-
-##### Example
-
-This example uses the [React `ref` feature](https://facebook.github.io/react/docs/refs-and-the-dom.html).
-
-```js
-class MyComponent extends Component {
- saySomething() {
- console.log('Hello, world!');
- }
-
- render() {
- // ...
- }
-}
-
-const MyGraphQLComponent = graphql(gql`query MyQuery { ... }`, { withRef: true })(
- MyComponent,
-);
-
-class MyContainerComponent extends Component {
- render() {
- return (
- {
- const wrappedInstance = component.getWrappedInstance();
- assert(wrappedInstance instanceof MyComponent);
- // We can call methods on the component class instance.
- wrappedInstance.saySomething();
- }}
- />
- );
- }
-}
-```
-
-#### `config.alias`
-
-Use this property to configure the name of your higher order component wrapper. For example, if you set `config.alias` to `'withCurrentUser'`, your wrapper component display name becomes `withCurrentUser(${WrappedComponent.displayName})` instead of `Apollo(${WrappedComponent.displayName})`.
-
-The default display name for React Apollo components is `Apollo(${WrappedComponent.displayName})`. This pattern is used by most React libraries that make use of higher order components. However, this might get confusing when you are using more than one higher order component and you look at the [React Devtools](https://camo.githubusercontent.com/42385f70ef638c48310ce01a675ceceb4d4b84a9/68747470733a2f2f64337676366c703535716a6171632e636c6f756466726f6e742e6e65742f6974656d732f30543361333532443366325330423049314e31662f53637265656e25323053686f74253230323031372d30312d3132253230617425323031362e33372e30302e706e673f582d436c6f75644170702d56697369746f722d49643d626536623231313261633434616130636135386432623562616265373336323626763d3236623964363434).
-
-
-##### Example
-
-This example uses the [`compose`](https://github.com/acdlite/recompose/blob/master/docs/API.md#compose) function to use multiple `graphql()` HOCs together.
-
-```js
-export default compose(
- graphql(gql`query MyQuery { ... }`, { alias: 'withCurrentUser' }),
- graphql(gql`query MyQuery { ... }`, { alias: 'withList' }),
-)(MyComponent);
-```
-
-## `graphql() options for queries`
-
-### `props.data`
-
-The higher-order component created with `graphql()` feeds a `data` prop into your component. Like so:
-
-```js
-render() {
- const { data } = this.props; // <- The `data` prop.
-}
-```
-
-The `data` prop contains the data fetched from your query in addition to some other useful information and functions to control the lifecycle of your GraphQL-connected component. So for example, if we had a query that looked like:
-
-```graphql
-query ViewerAndTodos {
- viewer {
- name
- }
- todos {
- text
- }
-}
-```
-
-Your `data` prop would contain that data:
-
-```js
-render() {
- const { data } = this.props;
-
- console.log(data.viewer); // <- The data returned by your query for `viewer`.
- console.log(data.todos); // <- The data returned by your query for `todos`.
-}
-```
-
-The `data` prop has some other useful properties which can be accessed directly from `data`. For example, `data.loading` or `data.error`. These properties are documented below.
-
-Make sure to always check `data.loading` and `data.error` in your components before rendering. Properties like `data.todos` which contain your app’s data may be undefined while your component is performing its initial fetch. Checking `data.loading` and `data.error` helps you avoid any issues with undefined data. Such checks may look like:
-
-```js
-render() {
- const { data: { loading, error, todos } } = this.props;
- if (loading) {
- return Loading...
;
- }
- if (error) {
- return Error!
;
- }
- return (
-
- {todos.map(({ id, text }) => (
- {text}
- ))}
-
- );
-}
-```
-
-### `data.loading`
-
-A boolean representing whether or not a query request is currently in flight for this component. This means that a query request has been sent using your network interface, and we have not yet gotten a response back. Use this property to render a loading component.
-
-However, just because `data.loading` is true it does not mean that you won’t have data. For instance, if you already have `data.todos`, but you want to get the latest todos from your API `data.loading` might be true, but you will still have the todos from your previous request.
-
-There are multiple different network states that your query may be in. If you want to see what the network state of your component is in more detail then refer to [`data.networkStatus`](#datanetworkstatus).
-
-**Example:**
-
-```js
-function MyComponent({ data: { loading } }) {
- if (loading) {
- return Loading...
;
- } else {
- // ...
- }
-}
-
-export default graphql(gql`query MyQuery { ... }`)(MyComponent);
-```
-
-### `data.error`
-
-If an error occurred then this property will be an instance of `ApolloError`. If you do not handle this error you will get a warning in your console that says something like: `"Unhandled (in react-apollo) Error: ..."`.
-
-**Example:**
-
-```js
-function MyComponent({ data: { error } }) {
- if (error) {
- return Error!
;
- } else {
- // ...
- }
-}
-
-export default graphql(gql`query MyComponentQuery { ... }`)(MyComponent);
-```
-
-### `data.networkStatus`
-
-`data.networkStatus` is useful if you want to display a different loading indicator (or no indicator at all) depending on your network status as it provides a more detailed view into the state of a network request on your component than [`data.loading`](#dataloading) does. `data.networkStatus` is an enum with different number values between 1 and 8. These number values each represent a different network state.
-
-1. `loading`: The query has never been run before and the request is now pending. A query will still have this network status even if a result was returned from the cache, but a query was dispatched anyway.
-2. `setVariables`: If a query’s variables change and a network request was fired then the network status will be `setVariables` until the result of that query comes back. React users will see this when [`options.variables`](#optionsvariables) changes on their queries.
-3. `fetchMore`: Indicates that `fetchMore` was called on this query and that the network request created is currently in flight.
-4. `refetch`: It means that `refetch` was called on a query and the refetch request is currently in flight.
-5. Unused.
-6. `poll`: Indicates that a polling query is currently in flight. So for example if you are polling a query every 10 seconds then the network status will switch to `poll` every 10 seconds whenever a poll request has been sent but not resolved.
-7. `ready`: No request is in flight for this query, and no errors happened. Everything is OK.
-8. `error`: No request is in flight for this query, but one or more errors were detected.
-
-If the network status is less then 7 then it is equivalent to [`data.loading`](#dataloading) being true. In fact you could replace all of your `data.loading` checks with `data.networkStatus < 7` and you would not see a difference. It is recommended that you use `data.loading`, however.
-
-**Example:**
-
-```js
-function MyComponent({ data: { networkStatus } }) {
- if (networkStatus === 6) {
- return Polling!
;
- } else if (networkStatus < 7) {
- return Loading...
;
- } else {
- // ...
- }
-}
-
-export default graphql(gql`query MyComponentQuery { ... }`)(MyComponent);
-```
-
-### `data.variables`
-
-The variables that Apollo used to fetch data from your GraphQL endpoint. This property is helpful if you want to render some information based on the variables that were used to make a request against your server.
-
-**Example:**
-
-```js
-function MyComponent({ data: { variables } }) {
- return (
-
- Query executed with the following variables:
- {JSON.stringify(variables)}
-
- );
-}
-
-export default graphql(gql`query MyComponentQuery { ... }`)(MyComponent);
-```
-
-### `data.refetch(variables)`
-
-Forces your component to refetch the query you defined in the `graphql()` function. This method is helpful when you want to reload the data in your component, or retry a fetch after an error.
-
-`data.refetch` returns a promise that resolves with the new data fetched from your API once the query has finished executing. The promise will reject if the query failed.
-
-The `data.refetch` function takes a single `variables` object argument. The `variables` argument will replace `variables` used with either the `query` option or the query from your `graphql()` HOC (depending on whether or not you specified a `query`) option to refetch the query you defined in the `graphql()` function.
-
-**Example:**
-
-```js
-function MyComponent({ data: { refetch } }) {
- return refetch()}>Reload ;
-}
-
-export default graphql(gql`query MyComponentQuery { ... }`)(MyComponent);
-```
-
-### `data.fetchMore(options)`
-
-The `data.fetchMore` function allows you to do pagination with your query component. To learn more about pagination with `data.fetchMore`, be sure to read the [pagination documentation](../../pagination/overview/).
-
-`data.fetchMore` returns a promise that resolves once the query executed to fetch more data has resolved.
-
-The `data.fetchMore` function takes a single `options` object argument. The `options` argument may take the following properties:
-
-- `[query]`: This is an optional GraphQL document created with the `gql` GraphQL tag. If you specify a `query` then that query will be fetched when you call `data.fetchMore`. If you do not specify a `query`, then the query from your `graphql()` HOC will be used.
-- `[variables]`: The optional variables you may provide that will be used with either the `query` option or the query from your `graphql()` HOC (depending on whether or not you specified a `query`).
-- `updateQuery(previousResult, { fetchMoreResult, variables })`: This is the required function you define that will actually update your paginated list. The first argument, `previousResult`, will be the previous data returned by the query you defined in your `graphql()` function. The second argument is an object with two properties, `fetchMoreResult` and `variables`. `fetchMoreResult` is the data returned by the new fetch that used the `query` and `variables` options from `data.fetchMore`. `variables` are the variables that were used when fetching more data. Using these arguments you should return a new data object with the same shape as the GraphQL query you defined in your `graphql()` function. See an example of this below, and also make sure to read the [pagination documentation](../../pagination/overview/).
-
-**Example:**
-
-```js
-data.fetchMore({
- updateQuery: (previousResult, { fetchMoreResult, variables }) => {
- return {
- ...previousResult,
- // Add the new feed data to the end of the old feed data.
- feed: [...previousResult.feed, ...fetchMoreResult.feed],
- };
- },
-});
-```
-
-### `data.subscribeToMore(options)`
-
-This function will set up a subscription, triggering updates whenever the server sends a subscription publication. This requires subscriptions to be set up on the server to properly work. Check out the [subscriptions guide](../../data/subscriptions/) for more information on getting this set up.
-
-This function returns an `unsubscribe` function handler which can be used to unsubscribe later.
-
-A common practice is to wrap the `subscribeToMore` call within `getDerivedStateFromProps` and perform the subscription after the original query has completed. To ensure the subscription isn't created multiple times, you can add it to component state. See the example for more details.
-
-- `[document]`: Document is a required property that accepts a GraphQL subscription created with the `gql` template string tag. It should contain a single GraphQL subscription operation with the data that will be returned.
-- `[variables]`: The optional variables you may provide that will be used with the `document` option.
-- `[updateQuery]`: An optional function that runs every time the server sends an update. This modifies the results of the HOC query. The first argument, `previousResult`, will be the previous data returned by the query you defined in your `graphql()` function. The second argument is an object with two properties. `subscriptionData` is result of the subscription. `variables` is the variables object used with the subscription query. Using these arguments you should return a new data object with the same shape as the GraphQL query you defined in your `graphql()` function. This is similar to the [`fetchMore`](#datafetchmoreoptions) callback.
-- `[onError]`: An optional error callback.
-
-In order to update the query's store with the result of the subscription, you must specify either the `updateQuery` option in `subscribeToMore` or the `reducer` option in your `graphql()` function.
-
-**Example:**
-
-```js
-class SubscriptionComponent extends Component {
- state = {
- subscriptionParam: null,
- unsubscribe: null,
- };
-
- static getDerivedStateFromProps(nextProps, prevState) {
- if (!nextProps.data.loading) {
- // Check for existing subscription
- if (prevState.unsubscribe) {
- // Only unsubscribe/update state if subscription variable has changed
- if (prevState.subscriptionParam === nextProps.subscriptionParam) {
- return null;
- }
- prevState.unsubscribe();
- }
-
- return {
- // Subscribe
- unsubscribe: nextProps.data.subscribeToMore({
- document: gql`subscription MySubscription {...}`,
- variables: {
- param: nextProps.subscriptionParam,
- },
- updateQuery: (previousResult, { subscriptionData, variables }) => {
- // Perform updates on previousResult with subscriptionData
- return updatedResult;
- },
- }),
- // Store subscriptionParam in state for next update
- subscriptionParam: nextProps.subscriptionParam,
- };
- }
-
- return null;
- }
-
- render() {
- ...
- }
-}
-```
-
-### `data.startPolling(interval)`
-
-This function will set up an interval and send a fetch request every time that interval ellapses. The function takes only one integer argument which allows you to configure how often you want your query to be executed in milliseconds. In other words, the `interval` argument represents the milliseconds between polls.
-
-Polling is a good way to keep the data in your UI fresh. By refetching your data every 5,000 milliseconds (or 5 seconds, for example) you may effectively emulate realtime data without needing to build up a realtime backend.
-
-If you call `data.startPolling` when your query is already polling then the current polling process will be cancelled and a new process will be started with the interval you specified.
-
-You may also use [`options.pollInterval`](#optionspollinterval) to start polling immediately after your component mounts. It is recommend that you use `options.pollInterval` if you don’t need to arbitrarily start and stop polling.
-
-If you set your `interval` to 0 then that means no polling instead of executing a request every JavaScript event loop tick.
-
-**Example:**
-
-```js
-class MyComponent extends Component {
- componentDidMount() {
- // In this specific case you may want to use `options.pollInterval` instead.
- this.props.data.startPolling(1000);
- }
-
- render() {
- // ...
- }
-}
-
-export default graphql(gql`query MyComponentQuery { ... }`)(MyComponent);
-```
-
-### `data.stopPolling()`
-
-By calling this function you will stop any current polling process. Your query will not start polling again until you call `data.startPolling`.
-
-**Example:**
-
-```js
-class MyComponent extends Component {
- render() {
- return (
-
- {
- this.props.data.startPolling(1000);
- }}
- >
- Start Polling
-
- {
- this.props.data.stopPolling();
- }}
- >
- Stop Polling
-
-
- );
- }
-}
-
-export default graphql(gql`query MyComponentQuery { ... }`)(MyComponent);
-```
-
-### `data.updateQuery(updaterFn)`
-
-This function allows you to update the data for your query outside of the context of any mutation, subscription, or fetch. This function only takes a single argument which will be another function. The argument function has the following signature:
-
-```
-(previousResult, { variables }) => nextResult
-```
-
-The first argument will be the data for your query that currently exists in the store, and you are expected to return a new data object with the same shape. That new data object will be written to the store and any components tracking that data will be updated reactively.
-
-The second argument is an object with a single property, `variables`. The `variables` property allows you to see what variables were used when reading the `previousResult` from the store.
-
-This method will _not_ update anything on the server. It will only update data in your client cache and if you reload your JavaScript environment then your update will disappear.
-
-**Example:**
-
-```js
-data.updateQuery(previousResult => ({
- ...previousResult,
- count: previousResult.count + 1,
-}));
-```
-
-### `config.options`
-
-An object or function that returns an object of options that are used to configure how the query is fetched and updated.
-
-If `config.options` is a function then it will take the component’s props as its first argument.
-
-The options available for use in this object depend on the operation type you pass in as the first argument to `graphql()`. The references below will document which options are available when your operation is a query. To see what other options are available for different operations, see the generic documentation for [`config.options`](#configoptions).
-
-**Example:**
-
-```js
-export default graphql(gql`query MyQuery { ... }`, {
- options: {
- // Options go here.
- },
-})(MyComponent);
-```
-
-```js
-export default graphql(gql`query MyQuery { ... }`, {
- options: props => ({
- // Options are computed from `props` here.
- }),
-})(MyComponent);
-```
-
-### `options.variables`
-
-The variables that will be used when executing the query operation. These variables should correspond with the variables that your query definition accepts. If you define `config.options` as a function then you may compute your variables from your props.
-
-**Example:**
-
-```js
-export default graphql(
- gql`
- query MyQuery ($width: Int!, $height: Int!) {
- ...
- }
-`,
- {
- options: props => ({
- variables: {
- width: props.size,
- height: props.size,
- },
- }),
- },
-)(MyComponent);
-```
-
-### `options.fetchPolicy`
-
-The fetch policy is an option that allows you to specify how you want your component to interact with the Apollo Client cache. By default, your component will try to read from the cache first, and if the full data for your query is in the cache then Apollo simply returns the data from the cache. If the full data for your query is _not_ in the cache then Apollo will execute your request using your network interface. By changing this option you can change this behavior.
-
-For a list of supported fetch policies, see [Setting a fetch policy](../../data/queries/#setting-a-fetch-policy).
-
-**Example:**
-
-```js
-export default graphql(gql`query MyQuery { ... }`, {
- options: { fetchPolicy: 'cache-and-network' },
-})(MyComponent);
-```
-
-### `options.errorPolicy`
-
-The error policy is an option which allows you to specify how you want your component to handle errors that can happen when fetching data from GraphQL. There are two types of errors that can happen during your request; a runtime error on the client or server which results in no data, or some GraphQL errors which may be delivered alongside actual data. In order to control how your UI interacts with these errors, you can use the error policy to tell Apollo when you want to know about GraphQL Errors or not!
-
-Valid `errorPolicy` values are:
-
-- `none`: This is the default value where we treat GraphQL errors as runtime errors. Apollo will discard any data that came back with the request and render your component with an `error` prop.
-- `ignore`: Much like `none`, this causes Apollo to ignore any data from your server, but it also won't update your UI aside from setting the loading state back to false.
-- `all`: Selecting all means you want to be notified any time there are any GraphQL errors. It will render your component with any data from the request and any errors with their information. It is particularly helpful for server side rendering so your UI always shows something
-
-**Example:**
-
-```js
-export default graphql(gql`query MyQuery { ... }`, {
- options: { errorPolicy: 'all' },
-})(MyComponent);
-```
-
-### `options.pollInterval`
-
-The interval in milliseconds at which you want to start polling. Whenever that number of milliseconds elapses your query will be executed using the network interface and another execution will be scheduled using the configured number of milliseconds.
-
-This option will start polling your query immediately when the component mounts. If you want to start and stop polling dynamically then you may use [`data.startPolling`](#datastartpollinginterval) and [`data.stopPolling`](#datastoppolling).
-
-If you set `options.pollInterval` to 0 then that means no polling instead of executing a request every JavaScript event loop tick.
-
-**Example:**
-
-```js
-export default graphql(gql`query MyQuery { ... }`, {
- options: { pollInterval: 5000 },
-})(MyComponent);
-```
-
-### `options.notifyOnNetworkStatusChange`
-
-Whether or not updates to the network status or network error should trigger re-rendering of your component.
-
-The default value is `false`.
-
-**Example:**
-
-```js
-export default graphql(gql`query MyQuery { ... }`, {
- options: { notifyOnNetworkStatusChange: true },
-})(MyComponent);
-```
-
-### `options.context`
-
-With the flexibility and power of [Apollo Link](../../networking/advanced-http-networking/) being part of Apollo Client, you may want to send information from your operation straight to a link in your network chain! This can be used to do things like set `headers` on HTTP requests from props, control which endpoint you send a query to, and so much more depending on what links your app is using. Everything under the `context` object gets passed directly to your network chain. For more information about using context, check out the [`HttpLink` context docs](../../networking/advanced-http-networking/)
-
-### `partialRefetch`
-
-If `true`, perform a query `refetch` if the query result is marked as being partial, and the returned data is reset to an empty Object by the Apollo Client `QueryManager` (due to a cache miss).
-
-The default value is `false` for backwards-compatibility's sake, but should be changed to true for most use-cases.
-
-**Example:**
-
-```js
-export default graphql(gql`query MyQuery { ... }`, {
- options: { partialRefetch: true },
-})(MyComponent);
-```
-
-## `graphql() options for mutations`
-
-### `props.mutate`
-
-The higher order component created when you pass a mutation to `graphql()` will provide your component with a single prop named `mutate`. Unlike the `data` prop which you get when you pass a query to `graphql()`, `mutate` is a function.
-
-The `mutate` function will actually execute your mutation using the network interface therefore mutating your data. The `mutate` function will also then update your cache in ways you define.
-
-To learn more about how mutations work, be sure to check out the [mutations usage documentation](../../data/mutations/).
-
-The `mutate` function accepts the same options that [`config.options`](#configoptions-2) for mutations accepts, so make sure to read through the documentation for that to know what you can pass into the `mutate` function.
-
-The reason the `mutate` function accepts the same options is that it will use the options from [`config.options`](#configoptions-2) _by default_. When you pass an object into the `mutate` function you are just overriding what is already in [`config.options`](#configoptions-2).
-
-**Example:**
-
-```js
-function MyComponent({ mutate }) {
- return (
- {
- mutate({
- variables: { foo: 42 },
- });
- }}
- >
- Mutate
-
- );
-}
-
-export default graphql(gql`mutation MyMutation { ... }`)(MyComponent);
-```
-
-### `config.options`
-
-An object or function that returns an object of options that are used to configure how the query is fetched and updated.
-
-If `config.options` is a function then it will take the component’s props as its first argument.
-
-The options available for use in this object depend on the operation type you pass in as the first argument to `graphql()`. The references below will document which options are available when your operation is a mutation. To see what other options are available for different operations, see the generic documentation for [`config.options`](#configoptions).
-
-The properties accepted in this options object may also be accepted by the [`props.mutate`](#propsmutate) function. Any options passed into the `mutate` function will take precedence over the options defined in the `config` object.
-
-**Example:**
-
-```js
-export default graphql(gql`mutation MyMutation { ... }`, {
- options: {
- // Options go here.
- },
-})(MyComponent);
-```
-
-```js
-export default graphql(gql`mutation MyMutation { ... }`, {
- options: props => ({
- // Options are computed from `props` here.
- }),
-})(MyComponent);
-```
-
-```js
-function MyComponent({ mutate }) {
- return (
- {
- mutate({
- // Options are component from `props` and component state here.
- });
- }}
- >
- Mutate
-
- );
-}
-
-export default graphql(gql`mutation MyMutation { ... }`)(MyComponent);
-```
-
-### `options.variables`
-
-The variables which will be used to execute the mutation operation. These variables should correspond to the variables that your mutation definition accepts. If you define `config.options` as a function, or you pass variables into the [`props.mutate`](#propsmutate) function then you may compute your variables from props and component state.
-
-**Example:**
-
-```js
-export default graphql(
- gql`
- mutation MyMutation ($foo: String!, $bar: String!) {
- ...
- }
-`,
- {
- options: props => ({
- variables: {
- foo: props.foo,
- bar: props.bar,
- },
- }),
- },
-)(MyComponent);
-```
-
-### `options.optimisticResponse`
-
-Often when you mutate data it is fairly easy to predict what the response of the mutation will be before asking your server. The optimistic response option allows you to make your mutations feel faster by simulating the result of your mutation in your UI before the mutation actually finishes.
-
-To learn more about the benefits of optimistic data and how to use it be sure to read the recipe on [Optimistic UI](../../performance/optimistic-ui/).
-
-This optimistic response will be used with [`options.update`](#optionsupdate) and [`options.updateQueries`](#optionsupdatequeries) to apply an update to your cache which will be rolled back before applying the update from the actual response.
-
-**Example:**
-
-```js
-function MyComponent({ newText, mutate }) {
- return (
- {
- mutate({
- variables: {
- text: newText,
- },
- // The optimistic response has all of the fields that are included in
- // the GraphQL mutation document below.
- optimisticResponse: {
- createTodo: {
- id: -1, // A temporary id. The server decides the real id.
- text: newText,
- completed: false,
- },
- },
- });
- }}
- >
- Add Todo
-
- );
-}
-
-export default graphql(gql`
- mutation CreateTodo ($text: String!) {
- createTodo(text: $text) {
- id
- text
- completed
- }
- }
-`)(MyComponent);
-```
-
-### `options.update`
-
-This option allows you to update your store based on your mutation’s result. By default Apollo Client will update all of the overlapping nodes in your store. Anything that shares the same id as returned by the `dataIdFromObject` you defined will be updated with the new fields from your mutation results. However, sometimes this alone is not sufficient. Sometimes you may want to update your cache in a way that is dependent on the data currently in your cache. For these updates you may use an `options.update` function.
-
-`options.update` takes two arguments. The first is an instance of a `DataProxy` object which has some methods which will allow you to interact with the data in your store. The second is the response from your mutation - either the optimistic response, or the actual response returned by your server (see the mutation result described in the [mutation render prop](./components/#render-prop-function-1) section for more details).
-
-In order to change the data in your store call methods on your `DataProxy` instance like [`writeQuery`](../../caching/cache-interaction/#writequery) and [`writeFragment`](../../caching/cache-interaction/#writefragment). This will update your cache and reactively re-render any of your GraphQL components which are querying affected data.
-
-To read the data from the store that you are changing, make sure to use methods on your `DataProxy` like [`readQuery`](../../caching/cache-interaction/#readquery) and [`readFragment`](../../caching/cache-interaction/#readfragment).
-
-For more information on updating your cache after a mutation with the `options.update` function make sure to read the [Apollo Client technical documentation on the subject](../../data/mutations/#updating-the-cache-directly).
-
-**Example:**
-
-```js
-const query = gql`query GetAllTodos { todos { ... } }`;
-
-export default graphql(
- gql`
- mutation CreateTodo ($text: String!) {
- createTodo(text: $text) { ... }
- }
-`,
- {
- options: {
- update: (proxy, { data: { createTodo } }) => {
- const data = proxy.readQuery({ query });
- data.todos.push(createTodo);
- proxy.writeQuery({ query, data });
- },
- },
- },
-)(MyComponent);
-```
-
-### `options.refetchQueries`
-
-Sometimes when you make a mutation you also want to update the data in your queries so that your users may see an up-to-date user interface. There are more fine-grained ways to update the data in your cache which include [`options.updateQueries`](#optionsupdatequeries), and [`options.update`](#optionsupdate). However, you can update the data in your cache more reliably at the cost of efficiency by using `options.refetchQueries`.
-
-`options.refetchQueries` will execute one or more queries using your network interface and will then normalize the results of those queries into your cache. Allowing you to potentially refetch queries you had fetched before, or fetch brand new queries.
-
-`options.refetchQueries` is either an array of strings or objects, or a function which takes the result of the mutation and returns an array of strings or objects.
-
-If `options.refetchQueries` is an array of strings then Apollo Client will look for any queries with the same names as the provided strings and will refetch those queries with their current variables. So for example if you have a GraphQL query component with a query named `Comments` (the query may look like: `query Comments { ... }`), and you pass an array of strings containing `Comments` to `options.refetchQueries` then the `Comments` query will be re-executed and when it resolves the latest data will be reflected in your UI.
-
-If `options.refetchQueries` is an array of objects then the objects must have two properties:
-
-- `query`: Query is a required property that accepts a GraphQL query created with the `gql` template string tag. It should contain a single GraphQL query operation that will be executed once the mutation has completed.
-- `[variables]`: Is an optional object of variables that is required when `query` accepts some variables.
-
-If an array of objects with this shape is specified then Apollo Client will refetch these queries with their variables.
-
-**Example:**
-
-```js
-export default graphql(gql`mutation MyMutation { ... }`, {
- options: {
- refetchQueries: ['CommentList', 'PostList'],
- },
-})(MyComponent);
-```
-
-```js
-import { COMMENT_LIST_QUERY } from '../components/CommentList';
-
-export default graphql(gql`mutation MyMutation { ... }`, {
- options: props => ({
- refetchQueries: [
- {
- query: COMMENT_LIST_QUERY,
- },
- {
- query: gql`
- query GetPostById ($id: ID!) {
- post(id: $id) {
- commentCount
- }
- }
- `,
- variables: {
- id: props.postID,
- },
- },
- ],
- }),
-})(MyComponent);
-```
-
-```js
-export default graphql(gql`mutation MyMutation { ... }`, {
- options: {
- refetchQueries: mutationResult => ['CommentList', 'PostList'],
- },
-})(MyComponent);
-```
-
-Please note that refetched queries are handled asynchronously, and by default are not necessarily completed before the mutation has completed. If you want to make sure refetched queries are completed before the mutation is considered done (or resolved), set [`options.awaitRefetchQueries`](#optionsawaitrefetchqueries) to `true`.
-
-### `options.awaitRefetchQueries`
-
-Queries refetched using [`options.refetchQueries`](#optionsrefetchqueries) are handled asynchronously, which means by default they are not necessarily completed before the mutation has completed. Setting `options.awaitRefetchQueries` to `true` will make sure refetched queries are completed before the mutation is considered done (or resolved). `options.awaitRefetchQueries` is `false` by default.
-
-### `options.updateQueries`
-
-**Note: We recommend using [`options.update`](#optionsupdate) instead of `updateQueries`. `updateQueries` will be removed in the next version of Apollo Client**
-
-This option allows you to update your store based on your mutation’s result. By default Apollo Client will update all of the overlapping nodes in your store. Anything that shares the same id as returned by the `dataIdFromObject` you defined will be updated with the new fields from your mutation results. However, sometimes this alone is not sufficient. Sometimes you may want to update your cache in a way that is dependent on the data currently in your cache. For these updates you may use an `options.updateQueries` function.
-
-`options.updateQueries` takes an object where query names are the keys and reducer functions are the values. If you are familiar with Redux, defining your `options.updateQueries` reducers is very similar to defining your Redux reducers. The object looks something like this:
-
-```js
-{
- Comments: (previousData, { mutationResult, queryVariables }) => nextData,
-}
-```
-
-Make sure that the key of your `options.updateQueries` object corresponds to an actual query that you have made somewhere else in your app. The query name will be the name you put after specifying the `query` operation type. So for example in the following query:
-
-```graphql
-query Comments {
- entry(id: 5) {
- comments {
- ...
- }
- }
-}
-```
-
-The query name would be `Comments`. If you have not executed a GraphQL query with the name of `Comments` before somewhere in your application, then the reducer function will never be run by Apollo and the key/value pair in `options.updateQueries` will be ignored.
-
-The first argument to the function you provide as the value for your object will be the previous data for your query. So if your key is `Comments` then the first argument will be the last data object that was returned for your `Comments` query, or the current object that is being rendered by any component using the `Comments` query.
-
-The second argument to your function value will be an object with three properties:
-
-- `mutationResult`: The `mutationResult` property will represent the result of your mutation after hitting the server. If you provided an [`options.optimisticResponse`](#optionsoptimisticresponse) then `mutationResult` may be that object.
-- `queryVariables`: The last set of variables that the query was executed with. This is helpful because when you specify the query name it will only update the data in the store for your current variable set.
-- `queryName`: This is the name of the query you are updating. It is the same name as the key you provided to `options.updateQueries`.
-
-The return value of your `options.updateQueries` functions _must_ have the same shape as your first `previousData` argument. However, you _must not_ mutate the `previousData` object. Instead you must create a new object with your changes. Just like in a Redux reducer.
-
-**Example:**
-
-```js
-export default graphql(
- gql`
- mutation SubmitComment ($text: String!) {
- submitComment(text: $text) { ... }
- }
-`,
- {
- options: {
- updateQueries: {
- Comments: (previousData, { mutationResult }) => {
- const newComment = mutationResult.data.submitComment;
- // Note how we return a new copy of `previousData` instead of mutating
- // it. This is just like a Redux reducer!
- return {
- ...previousData,
- entry: {
- ...previousData.entry,
- comments: [newComment, ...previousData.entry.comments],
- },
- };
- },
- },
- },
- },
-)(MyComponent);
-```
-
-## `withApollo(component)`
-
-```js
-import { withApollo } from '@apollo/client/react/hoc';
-```
-
-An enhancer that provides direct access to your [`ApolloClient`](../core/ApolloClient/) instance. This is useful if you want to do custom logic with Apollo, such as executing one-off queries. By calling this function with the component you want to enhance, `withApollo()` creates a new component that passes an instance of `ApolloClient` as a `client` prop.
-
-Most of the time you want to use `graphql()` instead of `withApollo()`. `graphql()` provides helpful features for working with your GraphQL data. You should only use `withApollo()` if you want the GraphQL client without any of these features.
-
-This will only be able to provide access to your client if there is an [` `](./hooks/#the-apolloprovider-component) component higher up in your tree to actually provide the client.
-
-**Example:**
-
-```js
-function MyComponent({ client }) {
- console.log(client);
-}
-
-export default withApollo(MyComponent);
-```
diff --git a/package-lock.json b/package-lock.json
index f568baca25f..b4b5f3c140a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,9 +15,7 @@
"@wry/equality": "^0.5.6",
"@wry/trie": "^0.5.0",
"graphql-tag": "^2.12.6",
- "hoist-non-react-statics": "^3.3.2",
"optimism": "^0.18.0",
- "prop-types": "^15.7.2",
"rehackt": "^0.1.0",
"response-iterator": "^0.2.6",
"symbol-observable": "^4.0.0",
@@ -47,7 +45,6 @@
"@types/bytes": "3.1.4",
"@types/fetch-mock": "7.3.8",
"@types/glob": "8.1.0",
- "@types/hoist-non-react-statics": "3.3.5",
"@types/jest": "29.5.12",
"@types/lodash": "4.17.7",
"@types/node": "20.16.1",
@@ -86,6 +83,7 @@
"patch-package": "8.0.0",
"pkg-pr-new": "0.0.24",
"prettier": "3.1.1",
+ "prop-types": "^15.7.2",
"react": "19.0.0",
"react-17": "npm:react@^17",
"react-18": "npm:react@^18",
@@ -4050,16 +4048,6 @@
"@types/node": "*"
}
},
- "node_modules/@types/hoist-non-react-statics": {
- "version": "3.3.5",
- "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
- "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
- "dev": true,
- "dependencies": {
- "@types/react": "*",
- "hoist-non-react-statics": "^3.3.0"
- }
- },
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz",
@@ -7932,14 +7920,6 @@
"hermes-estree": "0.20.1"
}
},
- "node_modules/hoist-non-react-statics": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
- "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
- "dependencies": {
- "react-is": "^16.7.0"
- }
- },
"node_modules/html-encoding-sniffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
@@ -9383,7 +9363,8 @@
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
},
"node_modules/js-yaml": {
"version": "3.14.1",
@@ -9744,6 +9725,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dev": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
@@ -10169,6 +10151,7 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -10902,6 +10885,7 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -11151,7 +11135,8 @@
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "dev": true
},
"node_modules/read-yaml-file": {
"version": "1.1.0",
diff --git a/package.json b/package.json
index e509e80e8c6..7b8d02741a8 100644
--- a/package.json
+++ b/package.json
@@ -98,9 +98,7 @@
"@wry/equality": "^0.5.6",
"@wry/trie": "^0.5.0",
"graphql-tag": "^2.12.6",
- "hoist-non-react-statics": "^3.3.2",
"optimism": "^0.18.0",
- "prop-types": "^15.7.2",
"rehackt": "^0.1.0",
"response-iterator": "^0.2.6",
"symbol-observable": "^4.0.0",
@@ -130,7 +128,6 @@
"@types/bytes": "3.1.4",
"@types/fetch-mock": "7.3.8",
"@types/glob": "8.1.0",
- "@types/hoist-non-react-statics": "3.3.5",
"@types/jest": "29.5.12",
"@types/lodash": "4.17.7",
"@types/node": "20.16.1",
@@ -169,6 +166,7 @@
"patch-package": "8.0.0",
"pkg-pr-new": "0.0.24",
"prettier": "3.1.1",
+ "prop-types": "^15.7.2",
"react": "19.0.0",
"react-17": "npm:react@^17",
"react-18": "npm:react@^18",
diff --git a/src/__tests__/__snapshots__/exports.ts.snap b/src/__tests__/__snapshots__/exports.ts.snap
index e21e634c864..77654247852 100644
--- a/src/__tests__/__snapshots__/exports.ts.snap
+++ b/src/__tests__/__snapshots__/exports.ts.snap
@@ -297,14 +297,6 @@ Array [
]
`;
-exports[`exports of public entry points @apollo/client/react/components 1`] = `
-Array [
- "Mutation",
- "Query",
- "Subscription",
-]
-`;
-
exports[`exports of public entry points @apollo/client/react/context 1`] = `
Array [
"ApolloConsumer",
@@ -314,16 +306,6 @@ Array [
]
`;
-exports[`exports of public entry points @apollo/client/react/hoc 1`] = `
-Array [
- "graphql",
- "withApollo",
- "withMutation",
- "withQuery",
- "withSubscription",
-]
-`;
-
exports[`exports of public entry points @apollo/client/react/hooks 1`] = `
Array [
"skipToken",
diff --git a/src/__tests__/exports.ts b/src/__tests__/exports.ts
index 6005727e782..ed948f33733 100644
--- a/src/__tests__/exports.ts
+++ b/src/__tests__/exports.ts
@@ -23,9 +23,7 @@ import * as linkUtils from "../link/utils";
import * as linkWS from "../link/ws";
import * as masking from "../masking";
import * as react from "../react";
-import * as reactComponents from "../react/components";
import * as reactContext from "../react/context";
-import * as reactHOC from "../react/hoc";
import * as reactHooks from "../react/hooks";
import * as reactInternal from "../react/internal";
import * as reactParser from "../react/parser";
@@ -71,9 +69,7 @@ describe("exports of public entry points", () => {
check("@apollo/client/link/ws", linkWS);
check("@apollo/client/masking", masking);
check("@apollo/client/react", react);
- check("@apollo/client/react/components", reactComponents);
check("@apollo/client/react/context", reactContext);
- check("@apollo/client/react/hoc", reactHOC);
check("@apollo/client/react/hooks", reactHooks);
check("@apollo/client/react/internal", reactInternal);
check("@apollo/client/react/parser", reactParser);
diff --git a/src/link/persisted-queries/__tests__/react.test.tsx b/src/link/persisted-queries/__tests__/react.test.tsx
index b05e7d98f32..0a1248057d3 100644
--- a/src/link/persisted-queries/__tests__/react.test.tsx
+++ b/src/link/persisted-queries/__tests__/react.test.tsx
@@ -10,9 +10,10 @@ import { ApolloProvider } from "../../../react/context";
import { InMemoryCache as Cache } from "../../../cache/inmemory/inMemoryCache";
import { ApolloClient } from "../../../core/ApolloClient";
import { createHttpLink } from "../../http/createHttpLink";
-import { graphql } from "../../../react/hoc/graphql";
import { getDataFromTree } from "../../../react/ssr/getDataFromTree";
import { createPersistedQueryLink as createPersistedQuery, VERSION } from "..";
+import { useQuery } from "../../../react";
+import { OperationVariables } from "../../../core";
function sha256(data: string) {
const hash = crypto.createHash("sha256");
@@ -88,11 +89,15 @@ describe("react application", () => {
ssrMode: true,
});
- const Query = graphql(query)(({
- data,
+ const Query = ({
children,
+ variables,
+ }: {
+ children: React.ReactNode;
+ variables: OperationVariables;
}) => {
- if (data!.loading) return null;
+ const { data, loading } = useQuery(query, { variables });
+ if (loading) return null;
return (
@@ -100,10 +105,10 @@ describe("react application", () => {
{children}
);
- });
+ };
const app = (
-
+
Hello!
@@ -135,7 +140,7 @@ describe("react application", () => {
const app2 = (
-
+
Hello!
diff --git a/src/react/components/Mutation.tsx b/src/react/components/Mutation.tsx
deleted file mode 100644
index ff122a7e648..00000000000
--- a/src/react/components/Mutation.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import * as PropTypes from "prop-types";
-import type * as ReactTypes from "react";
-
-import type { OperationVariables } from "../../core/index.js";
-import type { MutationComponentOptions } from "./types.js";
-import { useMutation } from "../hooks/index.js";
-
-/**
- * @deprecated
- * Official support for React Apollo render prop components ended in March 2020.
- * This library is still included in the `@apollo/client` package,
- * but it no longer receives feature updates or bug fixes.
- */
-export function Mutation(
- props: MutationComponentOptions
-): ReactTypes.JSX.Element | null {
- const [runMutation, result] = useMutation(props.mutation, props);
- return props.children ? props.children(runMutation, result) : null;
-}
-
-export interface Mutation {
- propTypes: PropTypes.InferProps>;
-}
-
-Mutation.propTypes = {
- mutation: PropTypes.object.isRequired,
- variables: PropTypes.object,
- optimisticResponse: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
- refetchQueries: PropTypes.oneOfType([
- PropTypes.arrayOf(
- PropTypes.oneOfType([PropTypes.string, PropTypes.object])
- ),
- PropTypes.func,
- ]),
- awaitRefetchQueries: PropTypes.bool,
- update: PropTypes.func,
- children: PropTypes.func.isRequired,
- onCompleted: PropTypes.func,
- onError: PropTypes.func,
- fetchPolicy: PropTypes.string,
-} as Mutation["propTypes"];
diff --git a/src/react/components/Query.tsx b/src/react/components/Query.tsx
deleted file mode 100644
index 428207784c7..00000000000
--- a/src/react/components/Query.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import * as PropTypes from "prop-types";
-import type * as ReactTypes from "react";
-
-import type { OperationVariables } from "../../core/index.js";
-import type { QueryComponentOptions } from "./types.js";
-import { useQuery } from "../hooks/index.js";
-
-/**
- * @deprecated
- * Official support for React Apollo render prop components ended in March 2020.
- * This library is still included in the `@apollo/client` package,
- * but it no longer receives feature updates or bug fixes.
- */
-export function Query<
- TData = any,
- TVariables extends OperationVariables = OperationVariables,
->(
- props: QueryComponentOptions
-): ReactTypes.JSX.Element | null {
- const { children, query, ...options } = props;
- const result = useQuery(query, options);
- return result ? children(result as any) : null;
-}
-
-export interface Query {
- propTypes: PropTypes.InferProps>;
-}
-
-Query.propTypes = {
- client: PropTypes.object,
- children: PropTypes.func.isRequired,
- fetchPolicy: PropTypes.string,
- notifyOnNetworkStatusChange: PropTypes.bool,
- onCompleted: PropTypes.func,
- onError: PropTypes.func,
- pollInterval: PropTypes.number,
- query: PropTypes.object.isRequired,
- variables: PropTypes.object,
- ssr: PropTypes.bool,
- partialRefetch: PropTypes.bool,
- returnPartialData: PropTypes.bool,
-} as Query["propTypes"];
diff --git a/src/react/components/Subscription.tsx b/src/react/components/Subscription.tsx
deleted file mode 100644
index 59d694156a5..00000000000
--- a/src/react/components/Subscription.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import * as PropTypes from "prop-types";
-import type * as ReactTypes from "react";
-
-import type { OperationVariables } from "../../core/index.js";
-import type { SubscriptionComponentOptions } from "./types.js";
-import { useSubscription } from "../hooks/index.js";
-
-/**
- * @deprecated
- * Official support for React Apollo render prop components ended in March 2020.
- * This library is still included in the `@apollo/client` package,
- * but it no longer receives feature updates or bug fixes.
- */
-export function Subscription<
- TData = any,
- TVariables extends OperationVariables = OperationVariables,
->(
- props: SubscriptionComponentOptions
-): ReactTypes.JSX.Element | null {
- const result = useSubscription(props.subscription, props);
- return props.children && result ? props.children(result) : null;
-}
-
-export interface Subscription {
- propTypes: PropTypes.InferProps<
- SubscriptionComponentOptions
- >;
-}
-
-Subscription.propTypes = {
- subscription: PropTypes.object.isRequired,
- variables: PropTypes.object,
- children: PropTypes.func,
- onSubscriptionData: PropTypes.func,
- onData: PropTypes.func,
- onSubscriptionComplete: PropTypes.func,
- onComplete: PropTypes.func,
- shouldResubscribe: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
-} as Subscription["propTypes"];
diff --git a/src/react/components/__tests__/client/Mutation.test.tsx b/src/react/components/__tests__/client/Mutation.test.tsx
deleted file mode 100644
index 7a88e64b5ae..00000000000
--- a/src/react/components/__tests__/client/Mutation.test.tsx
+++ /dev/null
@@ -1,1765 +0,0 @@
-import React, { useState, PropsWithChildren } from "react";
-import gql from "graphql-tag";
-import {
- ExecutionResult,
- FormattedExecutionResult,
- GraphQLError,
-} from "graphql";
-import userEvent from "@testing-library/user-event";
-import { render, screen, waitFor, act } from "@testing-library/react";
-
-import { ApolloClient } from "../../../../core";
-import { ApolloError } from "../../../../errors";
-import { DataProxy, InMemoryCache as Cache } from "../../../../cache";
-import { ApolloProvider } from "../../../context";
-import {
- itAsync,
- MockedProvider,
- MockLink,
- mockSingleLink,
-} from "../../../../testing";
-import { Query } from "../../Query";
-import { Mutation } from "../../Mutation";
-
-const mutation = gql`
- mutation createTodo($text: String!) {
- createTodo {
- id
- text
- completed
- __typename
- }
- __typename
- }
-`;
-
-type Data = {
- createTodo: {
- __typename: string;
- id: string;
- text: string;
- completed: boolean;
- };
- __typename: string;
-};
-
-const data: Data = {
- createTodo: {
- __typename: "Todo",
- id: "99",
- text: "This one was created with a mutation.",
- completed: true,
- },
- __typename: "Mutation",
-};
-
-const data2: Data = {
- createTodo: {
- __typename: "Todo",
- id: "100",
- text: "This one was created with a mutation.",
- completed: true,
- },
- __typename: "Mutation",
-};
-
-const mocks = [
- {
- request: { query: mutation },
- result: { data },
- },
- {
- request: { query: mutation },
- result: { data: data2 },
- },
-];
-
-const cache = new Cache({ addTypename: false });
-
-describe("General Mutation testing", () => {
- it("pick prop client over context client", async () => {
- const mock = (text: string) => [
- {
- request: { query: mutation },
- result: {
- data: {
- createTodo: {
- __typename: "Todo",
- id: "99",
- text,
- completed: true,
- },
- __typename: "Mutation",
- },
- },
- },
- {
- request: { query: mutation },
- result: {
- data: {
- createTodo: {
- __typename: "Todo",
- id: "100",
- text,
- completed: true,
- },
- __typename: "Mutation",
- },
- },
- },
- ];
-
- const mocksProps = mock("This is the result of the prop client mutation.");
- const mocksContext = mock(
- "This is the result of the context client mutation."
- );
-
- function mockClient(m: any) {
- return new ApolloClient({
- link: new MockLink(m, false),
- cache: new Cache({ addTypename: false }),
- });
- }
-
- const contextClient = mockClient(mocksContext);
- const propsClient = mockClient(mocksProps);
- const spy = jest.fn();
-
- const Component = (props: any) => {
- return (
-
-
- {(createTodo: any) => (
- createTodo().then(spy)}>Create
- )}
-
-
- );
- };
-
- const { rerender } = render( );
- await waitFor(
- () => {
- screen.getByText("Create");
- },
- { interval: 1 }
- );
-
- // context client mutation
- await userEvent.click(screen.getByText("Create"));
-
- await waitFor(
- () => {
- expect(spy).toHaveBeenCalledWith(mocksContext[0].result);
- },
- { interval: 1 }
- );
-
- // props client mutation
- rerender( );
-
- await userEvent.click(screen.getByText("Create"));
-
- await waitFor(
- () => {
- expect(spy).toHaveBeenCalledWith(mocksProps[0].result);
- },
- { interval: 1 }
- );
-
- // context client mutation
- rerender( );
- await userEvent.click(screen.getByText("Create"));
-
- await waitFor(
- () => {
- expect(spy).toHaveBeenCalledWith(mocksContext[1].result);
- },
- { interval: 1 }
- );
-
- // props client mutation
- rerender( );
- await userEvent.click(screen.getByText("Create"));
-
- await waitFor(
- () => {
- expect(spy).toHaveBeenCalledWith(mocksProps[1].result);
- },
- { interval: 1 }
- );
-
- await waitFor(
- () => {
- expect(spy).toHaveBeenCalledTimes(4);
- },
- { interval: 1 }
- );
- });
-
- itAsync("performs a mutation", (resolve, reject) => {
- let count = 0;
- const Component = () => (
-
- {(createTodo: any, result: any) => {
- try {
- if (count === 0) {
- expect(result.loading).toEqual(false);
- expect(result.called).toEqual(false);
- createTodo();
- } else if (count === 1) {
- expect(result.called).toEqual(true);
- expect(result.loading).toEqual(true);
- } else if (count === 2) {
- expect(result.called).toEqual(true);
- expect(result.loading).toEqual(false);
- expect(result.data).toEqual(data);
- }
- count++;
- } catch (err) {
- reject(err);
- }
- return
;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(count).toEqual(3);
- }).then(resolve, reject);
- });
-
- itAsync(
- "can bind only the mutation and not rerender by props",
- (resolve, reject) => {
- let count = 0;
- const Component = () => (
-
- {(createTodo: any, result: any) => {
- if (count === 0) {
- expect(result.loading).toEqual(false);
- expect(result.called).toEqual(false);
- setTimeout(() => {
- createTodo().then((r: any) => {
- expect(r!.data).toEqual(data);
- resolve();
- });
- });
- } else if (count === 1) {
- reject("rerender happened with ignoreResults turned on");
- }
- count++;
- return
;
- }}
-
- );
-
- render(
-
-
-
- );
- }
- );
-
- it("returns a resolved promise when calling the mutation function", async () => {
- let called = false;
- let result: any;
- const Component = () => (
-
- {(createTodo: any) => {
- if (!called) {
- createTodo().then((_result: any) => {
- result = _result;
- });
- }
- called = true;
-
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(result!.data).toEqual(data);
- });
- });
-
- it("returns rejected promise when calling the mutation function", async () => {
- let done = false;
- let called = false;
- const Component = () => (
-
- {(createTodo: any) => {
- if (!called) {
- createTodo().catch((error: any) => {
- expect(error).toEqual(
- new ApolloError({ networkError: new Error("Error 1") })
- );
- done = true;
- });
- }
-
- called = true;
- return null;
- }}
-
- );
-
- const mocksWithErrors = [
- {
- request: { query: mutation },
- error: new Error("Error 1"),
- },
- ];
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(done).toBe(true);
- });
- });
-
- it("only shows result for the latest mutation that is in flight", async () => {
- let count = 0;
-
- const onCompleted = (dataMutation: Data) => {
- if (count === 1) {
- expect(dataMutation).toEqual(data);
- } else if (count === 3) {
- expect(dataMutation).toEqual(data2);
- }
- };
- const Component = () => (
-
- {(createTodo: any, result: any) => {
- if (count === 0) {
- expect(result.called).toEqual(false);
- expect(result.loading).toEqual(false);
- createTodo();
- createTodo();
- } else if (count === 1) {
- expect(result.called).toEqual(true);
- expect(result.loading).toEqual(true);
- } else if (count === 2) {
- expect(result.loading).toEqual(false);
- expect(result.called).toEqual(true);
- expect(result.data).toEqual(data2);
- }
- count++;
- return
;
- }}
-
- );
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toBe(3);
- });
- });
-
- it("only shows the error for the latest mutation in flight", async () => {
- let count = 0;
-
- const onError = (error: Error) => {
- if (count === 1) {
- expect(error).toEqual(
- new ApolloError({ networkError: new Error("Error 1") })
- );
- } else if (count === 3) {
- expect(error).toEqual(
- new ApolloError({ networkError: new Error("Error 2") })
- );
- }
- };
- const Component = () => (
-
- {(createTodo: any, result: any) => {
- if (count === 0) {
- expect(result.called).toEqual(false);
- expect(result.loading).toEqual(false);
- createTodo();
- createTodo();
- } else if (count === 1) {
- expect(result.loading).toEqual(true);
- expect(result.called).toEqual(true);
- } else if (count === 2) {
- expect(result.loading).toEqual(false);
- expect(result.data).toEqual(undefined);
- expect(result.called).toEqual(true);
- expect(result.error).toEqual(
- new ApolloError({ networkError: new Error("Error 2") })
- );
- }
- count++;
- return
;
- }}
-
- );
-
- const mocksWithErrors = [
- {
- request: { query: mutation },
- error: new Error("Error 2"),
- },
- {
- request: { query: mutation },
- error: new Error("Error 2"),
- },
- ];
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toBe(3);
- });
- });
-
- it("calls the onCompleted prop as soon as the mutation is complete", async () => {
- let onCompletedCalled = false;
-
- class Component extends React.Component {
- state = {
- mutationDone: false,
- };
-
- onCompleted = (mutationData: Data) => {
- expect(mutationData).toEqual(data);
- onCompletedCalled = true;
- this.setState({
- mutationDone: true,
- });
- };
-
- render() {
- return (
-
- {(createTodo: any, result: any) => {
- if (!result.called) {
- expect(this.state.mutationDone).toBe(false);
- createTodo();
- }
- if (onCompletedCalled) {
- expect(this.state.mutationDone).toBe(true);
- }
- return null;
- }}
-
- );
- }
- }
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(onCompletedCalled).toEqual(true);
- });
- });
-
- it("renders result of the children render prop", () => {
- const Component = () => (
- {() => result
}
- );
-
- const { unmount } = render(
-
-
-
- );
- expect(screen.getByText("result")).toBeTruthy();
- // unmount here or else the mutation will resolve later and schedule an update that's not wrapped in act.
- unmount();
- });
-
- it("renders an error state", async () => {
- let count = 0;
- const Component = () => (
-
- {(createTodo: any, result: any) => {
- if (count === 0) {
- createTodo().catch((err: any) => {
- expect(err).toEqual(
- new ApolloError({ networkError: new Error("error occurred") })
- );
- });
- } else if (count === 1) {
- expect(result.loading).toBeTruthy();
- } else if (count === 2) {
- expect(result.error).toEqual(
- new ApolloError({ networkError: new Error("error occurred") })
- );
- }
- count++;
- return
;
- }}
-
- );
-
- const mockError = [
- {
- request: { query: mutation },
- error: new Error("error occurred"),
- },
- ];
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toEqual(3);
- });
- });
-
- it("renders an error state and throws when encountering graphql errors", async () => {
- let count = 0;
-
- const expectedError = new ApolloError({
- graphQLErrors: [new GraphQLError("error occurred")],
- });
-
- const Component = () => (
-
- {(createTodo: any, result: any) => {
- if (count === 0) {
- createTodo()
- .then(() => {
- throw new Error("Did not expect a result");
- })
- .catch((e: any) => {
- expect(e).toEqual(expectedError);
- });
- } else if (count === 1) {
- expect(result.loading).toBeTruthy();
- } else if (count === 2) {
- expect(result.error).toEqual(expectedError);
- }
- count++;
- return
;
- }}
-
- );
-
- const mockError = [
- {
- request: { query: mutation },
- result: {
- errors: [new GraphQLError("error occurred")],
- },
- },
- ];
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toEqual(3);
- });
- });
-
- it("renders an error state and does not throw when encountering graphql errors when errorPolicy=all", async () => {
- let count = 0;
- const Component = () => (
-
- {(createTodo: any, result: any) => {
- if (count === 0) {
- createTodo()
- .then((fetchResult: any) => {
- if (fetchResult && fetchResult.errors) {
- expect(fetchResult.errors.length).toEqual(1);
- expect(fetchResult.errors[0]).toEqual(
- new GraphQLError("error occurred")
- );
- } else {
- throw new Error(
- `Expected an object with array of errors but got ${fetchResult}`
- );
- }
- })
- .catch((e: any) => {
- throw e;
- });
- } else if (count === 1) {
- expect(result.loading).toBeTruthy();
- } else if (count === 2) {
- expect(result.error).toEqual(
- new ApolloError({
- graphQLErrors: [new GraphQLError("error occurred")],
- })
- );
- }
- count++;
- return
;
- }}
-
- );
-
- const mockError = [
- {
- request: { query: mutation },
- result: {
- errors: [new GraphQLError("error occurred")],
- },
- },
- ];
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toEqual(3);
- });
- });
-
- it("renders an error state and throws when encountering network errors when errorPolicy=all", async () => {
- let count = 0;
- const expectedError = new ApolloError({
- networkError: new Error("network error"),
- });
- const Component = () => (
-
- {(createTodo: any, result: any) => {
- if (count === 0) {
- createTodo()
- .then(() => {
- throw new Error("Did not expect a result");
- })
- .catch((e: any) => {
- expect(e).toEqual(expectedError);
- });
- } else if (count === 1) {
- expect(result.loading).toBeTruthy();
- } else if (count === 2) {
- expect(result.error).toEqual(expectedError);
- }
- count++;
- return
;
- }}
-
- );
-
- const mockError = [
- {
- request: { query: mutation },
- error: new Error("network error"),
- },
- ];
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toEqual(3);
- });
- });
-
- it("calls the onError prop if the mutation encounters an error", async () => {
- let onRenderCalled = false;
-
- class Component extends React.Component {
- state = {
- mutationError: false,
- };
-
- onError = (error: Error) => {
- expect(error.message).toMatch("error occurred");
- onRenderCalled = true;
- this.setState({ mutationError: true });
- };
-
- render() {
- const { mutationError } = this.state;
-
- return (
-
- {(createTodo: any, result: any) => {
- if (!result.called) {
- expect(mutationError).toBe(false);
- createTodo();
- }
- if (onRenderCalled) {
- expect(mutationError).toBe(true);
- }
- return null;
- }}
-
- );
- }
- }
-
- const mockError = [
- {
- request: { query: mutation },
- error: new Error("error occurred"),
- },
- ];
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(onRenderCalled).toEqual(true);
- });
- });
-
- it("performs a mutation with variables prop", async () => {
- const variables = {
- text: "play tennis",
- };
-
- let count = 0;
- const Component = () => (
-
- {(createTodo: any, result: any) => {
- if (count === 0) {
- createTodo();
- } else if (count === 1) {
- expect(result.loading).toEqual(true);
- expect(result.called).toEqual(true);
- } else if (count === 2) {
- expect(result.loading).toEqual(false);
- expect(result.called).toEqual(true);
- expect(result.data).toEqual(data);
- }
- count++;
- return
;
- }}
-
- );
-
- const mocks1 = [
- {
- request: { query: mutation, variables },
- result: { data },
- },
- ];
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toEqual(3);
- });
- });
-
- it("allows passing a variable to the mutate function", async () => {
- const variables = {
- text: "play tennis",
- };
-
- let count = 0;
- const Component = () => (
-
- {(createTodo: any, result: any) => {
- if (count === 0) {
- createTodo({ variables });
- } else if (count === 1) {
- expect(result.loading).toEqual(true);
- expect(result.called).toEqual(true);
- } else if (count === 2) {
- expect(result.loading).toEqual(false);
- expect(result.called).toEqual(true);
- expect(result.data).toEqual(data);
- }
- count++;
- return
;
- }}
-
- );
-
- const mocks1 = [
- {
- request: { query: mutation, variables },
- result: { data },
- },
- ];
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toEqual(3);
- });
- });
-
- it("allows an optimistic response prop", async () => {
- const link = mockSingleLink(...mocks);
- const client = new ApolloClient({
- link,
- cache,
- });
-
- const optimisticResponse = {
- createTodo: {
- id: "99",
- text: "This is an optimistic response",
- completed: false,
- __typename: "Todo",
- },
- __typename: "Mutation",
- };
-
- let count = 0;
- const Component = () => (
-
- {(createTodo: any, result: any) => {
- if (count === 0) {
- createTodo();
- const dataInStore = client.cache.extract(true);
- expect(dataInStore["Todo:99"]).toEqual(
- optimisticResponse.createTodo
- );
- } else if (count === 1) {
- expect(result.loading).toEqual(true);
- expect(result.called).toEqual(true);
- } else if (count === 2) {
- expect(result.loading).toEqual(false);
- expect(result.called).toEqual(true);
- expect(result.data).toEqual(data);
- }
- count++;
- return
;
- }}
-
- );
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toEqual(3);
- });
- });
-
- it("allows passing an optimistic response to the mutate function", async () => {
- const link = mockSingleLink(...mocks);
- const client = new ApolloClient({
- link,
- cache,
- });
-
- const optimisticResponse = {
- createTodo: {
- id: "99",
- text: "This is an optimistic response",
- completed: false,
- __typename: "Todo",
- },
- __typename: "Mutation",
- };
-
- let count = 0;
- const Component = () => (
-
- {(createTodo: any, result: any) => {
- if (count === 0) {
- createTodo({ optimisticResponse });
- const dataInStore = client.cache.extract(true);
- expect(dataInStore["Todo:99"]).toEqual(
- optimisticResponse.createTodo
- );
- } else if (count === 2) {
- expect(result.loading).toEqual(false);
- expect(result.called).toEqual(true);
- expect(result.data).toEqual(data);
- }
- count++;
- return
;
- }}
-
- );
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toEqual(3);
- });
- });
-
- it("allows a refetchQueries prop", async () => {
- const query = gql`
- query getTodo {
- todo {
- id
- text
- completed
- __typename
- }
- __typename
- }
- `;
-
- const queryData = {
- todo: {
- id: "1",
- text: "todo from query",
- completed: false,
- __typename: "Todo",
- },
- __typename: "Query",
- };
-
- const mocksWithQuery = [
- ...mocks,
- {
- request: { query },
- result: { data: queryData },
- },
- {
- request: { query },
- result: { data: queryData },
- },
- ];
-
- const refetchQueries = [
- {
- query,
- },
- ];
-
- let renderCount = 0;
- const Component = () => (
-
- {(createTodo: any, resultMutation: any) => (
-
- {(resultQuery: any) => {
- ++renderCount;
- if (renderCount === 1) {
- setTimeout(() => createTodo(), 10);
- } else if (renderCount === 2) {
- expect(resultMutation.loading).toBe(false);
- expect(resultQuery.loading).toBe(false);
- } else if (renderCount === 3) {
- expect(resultMutation.loading).toBe(true);
- expect(resultQuery.data).toEqual(queryData);
- } else if (renderCount === 4) {
- expect(resultMutation.loading).toBe(false);
- }
- return null;
- }}
-
- )}
-
- );
-
- render(
-
-
-
- );
- });
-
- it("allows a refetchQueries prop as string and variables have updated", async () => {
- const query = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
-
- const peopleData1 = {
- allPeople: {
- people: [{ name: "Luke Skywalker", __typename: "Person" }],
- __typename: "People",
- },
- };
- const peopleData2 = {
- allPeople: {
- people: [{ name: "Han Solo", __typename: "Person" }],
- __typename: "People",
- },
- };
- const peopleData3 = {
- allPeople: {
- people: [{ name: "Lord Vader", __typename: "Person" }],
- __typename: "People",
- },
- };
- const peopleMocks = [
- ...mocks,
- {
- request: { query, variables: { first: 1 } },
- result: { data: peopleData1 },
- },
- {
- request: { query, variables: { first: 2 } },
- result: { data: peopleData2 },
- },
- {
- request: { query, variables: { first: 2 } },
- result: { data: peopleData3 },
- },
- ];
-
- const refetchQueries = ["people"];
-
- let count = 0;
- let testFailures: any[] = [];
-
- const Component: React.FC>> = (
- props
- ) => {
- const [variables, setVariables] = useState(props.variables);
- return (
-
- {(createTodo: any, resultMutation: any) => (
-
- {(resultQuery: any) => {
- try {
- if (count === 0) {
- // "first: 1" loading
- expect(resultQuery.loading).toBe(true);
- expect(resultQuery.data).toBeUndefined();
- } else if (count === 1) {
- // "first: 1" loaded
- expect(resultQuery.loading).toBe(false);
- expect(resultQuery.data).toEqual(peopleData1);
- setTimeout(() => setVariables({ first: 2 }));
- } else if (count === 2) {
- expect(resultQuery.loading).toBe(true);
- expect(resultQuery.data).toBeUndefined();
- } else if (count === 3) {
- expect(resultQuery.loading).toBe(false);
- expect(resultQuery.data).toEqual(peopleData2);
- setTimeout(() => createTodo());
- } else if (count === 4) {
- // mutation loading
- expect(resultMutation.loading).toBe(true);
- } else if (count === 5) {
- // query refetched or mutation loaded
- // or both finished batched together
- // hard to make assumptions here
- } else if (count === 6) {
- // both loaded
- expect(resultQuery.loading).toBe(false);
- expect(resultMutation.loading).toBe(false);
- expect(resultQuery.data).toEqual(peopleData3);
- } else {
- throw new Error(`Too many renders (${count})`);
- }
- count++;
- } catch (err) {
- testFailures.push(err);
- }
- return null;
- }}
-
- )}
-
- );
- };
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- if (testFailures.length > 0) {
- throw testFailures[0];
- }
- expect(count).toEqual(7);
- });
- });
-
- it("allows refetchQueries to be passed to the mutate function", () =>
- new Promise((resolve, reject) => {
- const query = gql`
- query getTodo {
- todo {
- id
- text
- completed
- __typename
- }
- __typename
- }
- `;
-
- const queryData = {
- todo: {
- id: "1",
- text: "todo from query",
- completed: false,
- __typename: "Todo",
- },
- __typename: "Query",
- };
-
- const mocksWithQuery = [
- ...mocks,
- {
- request: { query },
- result: { data: queryData },
- },
- {
- request: { query },
- result: { data: queryData },
- },
- ];
-
- const refetchQueries = [
- {
- query,
- },
- ];
-
- let count = 0;
- const Component = () => (
-
- {(createTodo: any, resultMutation: any) => (
-
- {(resultQuery: any) => {
- try {
- if (count === 0) {
- setTimeout(() => createTodo({ refetchQueries }), 10);
- } else if (count === 1) {
- expect(resultMutation.loading).toBe(false);
- expect(resultQuery.loading).toBe(false);
- } else if (count === 2) {
- expect(resultMutation.loading).toBe(true);
- expect(resultQuery.data).toEqual(queryData);
- } else if (count === 3) {
- expect(resultMutation.loading).toBe(false);
- }
- count++;
- } catch (err) {
- reject(err);
- }
- return null;
- }}
-
- )}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(count).toBe(4);
- }).then(resolve, reject);
- }));
-
- it("has an update prop for updating the store after the mutation", async () => {
- const update = (_proxy: DataProxy, response: FormattedExecutionResult) => {
- expect(response.data).toEqual(data);
- };
-
- let count = 0;
- const Component = () => (
-
- {(createTodo: any) => {
- if (count === 0) {
- createTodo().then((response: any) => {
- expect(response!.data).toEqual(data);
- });
- }
- count++;
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toBe(2);
- });
- });
-
- it("allows update to be passed to the mutate function", async () => {
- const update = (_proxy: DataProxy, response: ExecutionResult) => {
- expect(response.data).toEqual(data);
- };
-
- let count = 0;
- const Component = () => (
-
- {(createTodo: any) => {
- if (count === 0) {
- createTodo({ update }).then((response: any) => {
- expect(response!.data).toEqual(data);
- });
- }
- count++;
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toBe(3);
- });
- });
-
- it("allows for overriding the options passed in the props by passing them in the mutate function", async () => {
- const variablesProp = {
- text: "play tennis",
- };
-
- const variablesMutateFn = {
- text: "go swimming",
- };
-
- let count = 0;
- const Component = () => (
-
- {(createTodo: any, result: any) => {
- if (count === 0) {
- createTodo({ variables: variablesMutateFn });
- } else if (count === 2) {
- expect(result.loading).toEqual(false);
- expect(result.called).toEqual(true);
- expect(result.data).toEqual(data2);
- }
- count++;
- return
;
- }}
-
- );
-
- const mocks1 = [
- {
- request: { query: mutation, variables: variablesProp },
- result: { data },
- },
- {
- request: { query: mutation, variables: variablesMutateFn },
- result: { data: data2 },
- },
- ];
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toBe(3);
- });
- });
-
- it("updates if the client changes", async () => {
- const link1 = mockSingleLink({
- request: { query: mutation },
- result: { data },
- });
- const client1 = new ApolloClient({
- link: link1,
- cache: new Cache({ addTypename: false }),
- });
-
- const data3 = {
- createTodo: {
- __typename: "Todo",
- id: "100",
- text: "After updating client.",
- completed: false,
- },
- __typename: "Mutation",
- };
-
- const link2 = mockSingleLink({
- request: { query: mutation },
- result: { data: data3 },
- });
-
- const client2 = new ApolloClient({
- link: link2,
- cache: new Cache({ addTypename: false }),
- });
-
- let count = 0;
- class Component extends React.Component {
- state = {
- client: client1,
- };
-
- render() {
- return (
-
-
- {(createTodo: any, result: any) => {
- if (count === 0) {
- expect(result.called).toEqual(false);
- expect(result.loading).toEqual(false);
- setTimeout(createTodo, 10);
- } else if (count === 2 && result) {
- expect(result.data).toEqual(data);
- setTimeout(() => {
- this.setState({
- client: client2,
- });
- });
- } else if (count === 3) {
- expect(result.loading).toEqual(false);
- setTimeout(createTodo, 10);
- } else if (count === 5) {
- expect(result.data).toEqual(data3);
- }
- count++;
- return null;
- }}
-
-
- );
- }
- }
-
- render( );
-
- await waitFor(() => {
- expect(count).toBe(6);
- });
- });
-
- it("uses client from props instead of one provided by context", () => {
- const link1 = mockSingleLink({
- request: { query: mutation },
- result: { data },
- });
- const client1 = new ApolloClient({
- link: link1,
- cache: new Cache({ addTypename: false }),
- });
-
- const link2 = mockSingleLink({
- request: { query: mutation },
- result: { data: data2 },
- });
- const client2 = new ApolloClient({
- link: link2,
- cache: new Cache({ addTypename: false }),
- });
-
- let count = 0;
-
- render(
-
-
- {(createTodo: any, result: any) => {
- if (!result.called) {
- act(() => {
- createTodo();
- });
- }
-
- if (count === 2) {
- expect(result.loading).toEqual(false);
- expect(result.called).toEqual(true);
- expect(result.data).toEqual(data2);
- }
-
- count++;
- return
;
- }}
-
-
- );
-
- return waitFor(() => {
- expect(count).toBe(3);
- });
- });
-
- it("errors if a query is passed instead of a mutation", () => {
- const query = gql`
- query todos {
- todos {
- id
- }
- }
- `;
-
- // Prevent error from being logged in console of test.
- const errorLogger = console.error;
- console.error = () => {};
-
- expect(() => {
- render(
-
- {() => null}
-
- );
- }).toThrowError(
- "Running a Mutation requires a graphql Mutation, but a Query was used " +
- "instead."
- );
-
- console.log = errorLogger;
- });
-
- it("errors when changing from mutation to a query", async () => {
- let didError = false;
- const query = gql`
- query todos {
- todos {
- id
- }
- }
- `;
-
- class Component extends React.Component {
- state = {
- query: mutation,
- };
-
- componentDidCatch(e: Error) {
- expect(e).toEqual(
- new Error(
- "Running a Mutation requires a graphql Mutation, but a Query " +
- "was used instead."
- )
- );
- didError = true;
- }
- render() {
- return (
-
- {() => {
- setTimeout(() => {
- this.setState({
- query,
- });
- });
- return null;
- }}
-
- );
- }
- }
-
- // Prevent error from being logged in console of test.
- const errorLogger = console.error;
- console.error = () => {};
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- // TODO(fixme): The following line fixes the RTL lint rule error:
- //
- // expect(waitFor(() => didError)).resolves.toBe(true);
- //
- // ...however it also causes the test to fail against React 17.
- // eslint-disable-next-line testing-library/await-async-utils
- expect(didError).toBe(true);
- });
-
- console.log = errorLogger;
- });
-
- it("errors if a subscription is passed instead of a mutation", () => {
- const subscription = gql`
- subscription todos {
- todos {
- id
- }
- }
- `;
-
- // Prevent error from being logged in console of test.
- const errorLogger = console.error;
- console.error = () => {};
-
- expect(() => {
- render(
-
- {() => null}
-
- );
- }).toThrowError(
- "Running a Mutation requires a graphql Mutation, but a Subscription " +
- "was used instead."
- );
-
- console.log = errorLogger;
- });
-
- it("errors when changing from mutation to a subscription", async () => {
- let didError = false;
- const subscription = gql`
- subscription todos {
- todos {
- id
- }
- }
- `;
-
- class Component extends React.Component {
- state = {
- query: mutation,
- };
-
- componentDidCatch(e: Error) {
- expect(e).toEqual(
- new Error(
- "Running a Mutation requires a graphql Mutation, but a " +
- "Subscription was used instead."
- )
- );
- didError = true;
- }
-
- render() {
- return (
-
- {() => {
- setTimeout(() => {
- this.setState({
- query: subscription,
- });
- });
- return null;
- }}
-
- );
- }
- }
-
- // Prevent error from being logged in console of test.
- const errorLogger = console.error;
- console.error = () => {};
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- // TODO(fixme): The following line fixes the RTL lint rule error:
- //
- // expect(waitFor(() => didError)).resolves.toBe(true);
- //
- // ...however it also causes the test to fail against React 17.
- // eslint-disable-next-line testing-library/await-async-utils
- expect(didError).toBe(true);
- });
- console.log = errorLogger;
- });
-
- describe("after it has been unmounted", () => {
- it("calls the onCompleted prop after the mutation is complete", async () => {
- let finished = false;
- let success = false;
- const context = { foo: "bar" };
- const onCompletedFn = jest.fn();
- const checker = () => {
- setTimeout(() => {
- success = true;
- expect(onCompletedFn).toHaveBeenCalledWith(
- data,
- expect.objectContaining({ context })
- );
- }, 100);
- };
-
- class Component extends React.Component {
- state = {
- called: false,
- };
-
- render() {
- const { called } = this.state;
- if (called === true) {
- return null;
- } else {
- return (
-
- {(createTodo: any) => {
- setTimeout(() => {
- createTodo({ context }).finally(() => {
- finished = true;
- });
- expect(onCompletedFn).toHaveBeenCalledWith;
- // eslint-disable-next-line testing-library/await-async-utils
- this.setState({ called: true }, checker);
- });
- return null;
- }}
-
- );
- }
- }
- }
-
- render(
-
-
-
- );
-
- await waitFor(
- () => {
- // TODO(fixme): The following line fixes the RTL lint rule error:
- //
- // expect(waitFor(() => finished)).resolves.toBe(true);
- //
- // ...however it also causes the test to fail against React 17.
- // eslint-disable-next-line testing-library/await-async-utils
- expect(finished).toBe(true);
- },
- { interval: 1 }
- );
- await waitFor(
- () => {
- // TODO(fixme): The following line fixes the RTL lint rule error:
- //
- // expect(waitFor(() => success)).resolves.toBe(true);
- //
- // ...however it also causes the test to fail against React 17.
- // eslint-disable-next-line testing-library/await-async-utils
- expect(success).toBe(true);
- },
- { interval: 1 }
- );
- });
- });
-
- it("calls the onError prop if the mutation encounters an error", async () => {
- let finished = false;
- let onErrorCalled = false;
- function onError(error: ApolloError) {
- expect(error.message).toEqual("error occurred");
- onErrorCalled = true;
- }
-
- function Component() {
- return (
-
- {(createTodo: any, { called }: { called: boolean }) => {
- if (!called) {
- createTodo().finally(() => {
- finished = true;
- });
- }
- return null;
- }}
-
- );
- }
-
- const mockError = [
- {
- request: { query: mutation },
- error: new Error("error occurred"),
- },
- ];
-
- render(
-
-
-
- );
-
- await waitFor(
- () => {
- // TODO(fixme): The following line fixes the RTL lint rule error:
- //
- // expect(waitFor(() => onErrorCalled)).resolves.toBe(true);
- //
- // ...however it also causes the test to fail against React 17.
- // eslint-disable-next-line testing-library/await-async-utils
- expect(onErrorCalled).toBe(true);
- },
- { interval: 1 }
- );
- await waitFor(
- () => {
- // TODO(fixme): The following line fixes the RTL lint rule error:
- //
- // expect(waitFor(() => finished)).resolves.toBe(true);
- //
- // ...however it also causes the test to fail against React 17.
- // eslint-disable-next-line testing-library/await-async-utils
- expect(finished).toBe(true);
- },
- { interval: 1 }
- );
- });
-});
diff --git a/src/react/components/__tests__/client/Query.test.tsx b/src/react/components/__tests__/client/Query.test.tsx
deleted file mode 100644
index 624d38f137d..00000000000
--- a/src/react/components/__tests__/client/Query.test.tsx
+++ /dev/null
@@ -1,2195 +0,0 @@
-import React from "react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-import { render, screen, waitFor } from "@testing-library/react";
-
-import { ApolloClient, NetworkStatus } from "../../../../core";
-import { ApolloError } from "../../../../errors";
-import { ApolloLink } from "../../../../link/core";
-import { InMemoryCache } from "../../../../cache";
-import { ApolloProvider } from "../../../context";
-import { itAsync, MockedProvider, mockSingleLink } from "../../../../testing";
-import { Query } from "../../Query";
-import { QueryResult } from "../../../types/types";
-import {
- disableActEnvironment,
- createRenderStream,
-} from "@testing-library/react-render-stream";
-
-const allPeopleQuery: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
-`;
-
-interface Data {
- allPeople: {
- people: Array<{ name: string }>;
- };
-}
-
-const allPeopleData: Data = {
- allPeople: { people: [{ name: "Luke Skywalker" }] },
-};
-const allPeopleMocks = [
- {
- request: { query: allPeopleQuery },
- result: { data: allPeopleData },
- },
-];
-
-const AllPeopleQuery = Query;
-
-describe("Query component", () => {
- itAsync("calls the children prop", (resolve, reject) => {
- let finished = false;
- const link = mockSingleLink({
- request: { query: allPeopleQuery },
- result: { data: allPeopleData },
- });
- const client = new ApolloClient({
- link,
- cache: new InMemoryCache({ addTypename: false }),
- });
-
- const Component = () => (
-
- {(result: any) => {
- const {
- client: clientResult,
- observable,
- fetchMore,
- refetch,
- reobserve,
- startPolling,
- stopPolling,
- subscribeToMore,
- updateQuery,
- ...rest
- } = result;
- try {
- if (result.loading) {
- expect(rest).toEqual({
- called: true,
- loading: true,
- networkStatus: 1,
- previousData: undefined,
- variables: {},
- });
- expect(clientResult).toBe(client);
- } else {
- expect(rest).toEqual({
- called: true,
- data: {
- allPeople: {
- people: [
- {
- name: "Luke Skywalker",
- },
- ],
- },
- },
- error: undefined,
- loading: false,
- networkStatus: 7,
- previousData: undefined,
- variables: {},
- });
- finished = true;
- }
- } catch (err) {
- reject(err);
- }
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(finished).toBe(true);
- }).then(resolve, reject);
- });
-
- it("renders using the children prop", async () => {
- const Component = () => (
- {(_: any) => test
}
- );
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(screen.getByText("test")).toBeTruthy();
- });
- });
-
- describe("result provides", () => {
- let consoleWarn = console.warn;
- beforeAll(() => {
- console.warn = () => null;
- });
-
- afterAll(() => {
- console.warn = consoleWarn;
- });
-
- itAsync("client", (resolve, reject) => {
- let count = 0;
- const queryWithVariables: DocumentNode = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
-
- const mocksWithVariable = [
- {
- request: {
- query: queryWithVariables,
- variables: {
- first: 1,
- },
- },
- result: { data: allPeopleData },
- },
- ];
-
- const variables = {
- first: 1,
- };
-
- const Component = () => (
-
- {({ client }: any) => {
- ++count;
- try {
- expect(client).not.toBeFalsy();
- expect(client.version).not.toBeFalsy();
- } catch (error) {
- reject(error);
- }
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(count).toBe(2);
- }).then(resolve, reject);
- });
-
- itAsync("error", (resolve, reject) => {
- let finished = false;
- const mockError = [
- {
- request: { query: allPeopleQuery },
- error: new Error("error occurred"),
- },
- ];
-
- const Component = () => (
-
- {(result: any) => {
- if (result.loading) {
- return null;
- }
- try {
- expect(result.error).toEqual(
- new ApolloError({ networkError: new Error("error occurred") })
- );
- finished = true;
- } catch (error) {
- reject(error);
- }
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(finished).toBe(true);
- }).then(resolve, reject);
- });
-
- itAsync("refetch", (resolve, reject) => {
- const queryRefetch: DocumentNode = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
-
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const data2 = { allPeople: { people: [{ name: "Han Solo" }] } };
- const data3 = { allPeople: { people: [{ name: "Darth Vader" }] } };
-
- const refetchVariables = {
- first: 1,
- };
-
- const mocks = [
- {
- request: { query: queryRefetch, variables: refetchVariables },
- result: { data: data1 },
- },
- {
- request: { query: queryRefetch, variables: refetchVariables },
- result: { data: data2 },
- },
- {
- request: { query: queryRefetch, variables: { first: 2 } },
- result: { data: data3 },
- },
- ];
-
- let count = 0;
- let hasRefetched = false;
-
- const Component = () => (
-
- {(result: any) => {
- const { data, loading } = result;
- if (loading) {
- count++;
- return null;
- }
-
- try {
- if (count === 1) {
- // first data
- expect(data).toEqual(data1);
- }
- if (count === 3) {
- // second data
- expect(data).toEqual(data2);
- }
- if (count === 5) {
- // third data
- expect(data).toEqual(data3);
- }
- } catch (error) {
- reject(error);
- }
-
- count++;
- if (hasRefetched) {
- return null;
- }
-
- hasRefetched = true;
- setTimeout(() => {
- result
- .refetch()
- .then((result1: any) => {
- expect(result1.data).toEqual(data2);
- return result.refetch({ first: 2 });
- })
- .then((result2: any) => {
- expect(result2.data).toEqual(data3);
- })
- .catch(reject);
- });
-
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(count).toBe(6);
- }).then(resolve, reject);
- });
-
- itAsync("fetchMore", (resolve, reject) => {
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const data2 = { allPeople: { people: [{ name: "Han Solo" }] } };
-
- const variables = {
- first: 2,
- };
-
- const mocks = [
- {
- request: { query: allPeopleQuery, variables: { first: 2 } },
- result: { data: data1 },
- },
- {
- request: { query: allPeopleQuery, variables: { first: 1 } },
- result: { data: data2 },
- },
- ];
-
- let count = 0;
-
- const Component = () => (
-
- {(result: any) => {
- if (result.loading) {
- return null;
- }
- if (count === 0) {
- setTimeout(() => {
- result
- .fetchMore({
- variables: { first: 1 },
- updateQuery: (prev: any, { fetchMoreResult }: any) =>
- fetchMoreResult ?
- {
- allPeople: {
- people: [
- ...prev.allPeople.people,
- ...fetchMoreResult.allPeople.people,
- ],
- },
- }
- : prev,
- })
- .then((result2: any) => {
- expect(result2.data).toEqual(data2);
- })
- .catch(reject);
- });
- } else if (count === 1) {
- try {
- expect(result.data).toEqual({
- allPeople: {
- people: [
- ...data1.allPeople.people,
- ...data2.allPeople.people,
- ],
- },
- });
- } catch (error) {
- reject(error);
- }
- }
-
- count++;
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => expect(count).toBe(2)).then(resolve, reject);
- });
-
- itAsync("startPolling", (resolve, reject) => {
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const data2 = { allPeople: { people: [{ name: "Han Solo" }] } };
- const data3 = { allPeople: { people: [{ name: "Darth Vader" }] } };
-
- const mocks = [
- {
- request: { query: allPeopleQuery },
- result: { data: data1 },
- },
- {
- request: { query: allPeopleQuery },
- result: { data: data2 },
- },
- {
- request: { query: allPeopleQuery },
- result: { data: data3 },
- },
- ];
-
- let count = 0;
- let isPolling = false;
-
- const POLL_INTERVAL = 5;
-
- let unmount: any;
- const Component = () => (
-
- {(result: any) => {
- if (result.loading) {
- return null;
- }
- if (!isPolling) {
- isPolling = true;
- result.startPolling(POLL_INTERVAL);
- }
-
- try {
- if (count === 0) {
- expect(result.data).toEqual(data1);
- } else if (count === 1) {
- expect(result.data).toEqual(data2);
- } else if (count === 2) {
- expect(result.data).toEqual(data3);
- setTimeout(unmount);
- }
- } catch (error) {
- reject(error);
- }
-
- count++;
- return null;
- }}
-
- );
-
- unmount = render(
-
-
-
- ).unmount;
-
- waitFor(() => expect(count).toBe(3)).then(resolve, reject);
- });
-
- itAsync("stopPolling", (resolve, reject) => {
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const data2 = { allPeople: { people: [{ name: "Han Solo" }] } };
- const data3 = { allPeople: { people: [{ name: "Darth Vader" }] } };
-
- const mocks = [
- {
- request: { query: allPeopleQuery },
- result: { data: data1 },
- },
- {
- request: { query: allPeopleQuery },
- result: { data: data2 },
- },
- {
- request: { query: allPeopleQuery },
- result: { data: data3 },
- },
- ];
-
- const POLL_COUNT = 2;
- const POLL_INTERVAL = 5;
- let count = 0;
-
- const Component = () => (
-
- {(result: any) => {
- if (result.loading) {
- return null;
- }
- if (count === 0) {
- expect(result.data).toEqual(data1);
- } else if (count === 1) {
- expect(result.data).toEqual(data2);
- result.stopPolling();
- }
- count++;
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => expect(count).toBe(POLL_COUNT)).then(resolve, reject);
- });
-
- itAsync("updateQuery", (resolve, reject) => {
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const data2 = { allPeople: { people: [{ name: "Han Solo" }] } };
- const variables = {
- first: 2,
- };
- const mocks = [
- {
- request: { query: allPeopleQuery, variables },
- result: { data: data1 },
- },
- ];
-
- let isUpdated = false;
-
- let count = 0;
- const Component = () => (
-
- {(result: any) => {
- if (result.loading) {
- return null;
- }
-
- if (isUpdated) {
- try {
- expect(result.data).toEqual(data2);
- } catch (error) {
- reject(error);
- }
- return null;
- }
-
- isUpdated = true;
- setTimeout(() => {
- result.updateQuery(
- (prev: any, { variables: variablesUpdate }: any) => {
- count += 1;
- try {
- expect(prev).toEqual(data1);
- expect(variablesUpdate).toEqual({ first: 2 });
- } catch (error) {
- reject(error);
- }
- return data2;
- }
- );
- });
-
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => expect(count).toBe(1)).then(resolve, reject);
- });
- });
-
- describe("props allow", () => {
- it("custom fetch-policy", async () => {
- let count = 0;
- const Component = () => (
-
- {(result: any) => {
- if (!result.loading) {
- expect(result.networkStatus).toBe(NetworkStatus.ready);
- }
- count += 1;
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toBe(2);
- });
- });
-
- it("default fetch-policy", async () => {
- let count = 0;
- const Component = () => (
-
- {(result: any) => {
- if (!result.loading) {
- expect(result.networkStatus).toBe(NetworkStatus.ready);
- }
- count += 1;
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toBe(2);
- });
- });
-
- itAsync("notifyOnNetworkStatusChange", (resolve, reject) => {
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const data2 = { allPeople: { people: [{ name: "Han Solo" }] } };
-
- const mocks = [
- {
- request: { query: allPeopleQuery },
- result: { data: data1 },
- },
- {
- request: { query: allPeopleQuery },
- result: { data: data2 },
- },
- ];
-
- let count = 0;
- const Component = () => (
-
- {(result: any) => {
- try {
- if (count === 0) {
- expect(result.loading).toBeTruthy();
- }
- if (count === 1) {
- expect(result.loading).toBeFalsy();
- setTimeout(() => {
- result.refetch();
- });
- }
- if (count === 2) {
- expect(result.loading).toBeTruthy();
- }
- if (count === 3) {
- expect(result.loading).toBeFalsy();
- }
-
- count++;
- } catch (error) {
- reject(error);
- }
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(count).toBe(4);
- }).then(resolve, reject);
- });
-
- itAsync("pollInterval", (resolve, reject) => {
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const data2 = { allPeople: { people: [{ name: "Han Solo" }] } };
- const data3 = { allPeople: { people: [{ name: "Darth Vader" }] } };
-
- const mocks = [
- {
- request: { query: allPeopleQuery },
- result: { data: data1 },
- },
- {
- request: { query: allPeopleQuery },
- result: { data: data2 },
- },
- {
- request: { query: allPeopleQuery },
- result: { data: data3 },
- },
- ];
-
- let count = 0;
- const POLL_COUNT = 3;
- const POLL_INTERVAL = 30;
-
- const Component = () => (
-
- {(result: any) => {
- if (result.loading) {
- return null;
- }
- if (count === 0) {
- expect(result.data).toEqual(data1);
- } else if (count === 1) {
- expect(result.data).toEqual(data2);
- } else if (count === 2) {
- expect(result.data).toEqual(data3);
- result.stopPolling();
- }
- count++;
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => expect(count).toBe(POLL_COUNT)).then(resolve, reject);
- });
-
- itAsync("skip", (resolve, reject) => {
- let finished = false;
- const Component = () => (
-
- {(result: any) => {
- try {
- expect(result.loading).toBeFalsy();
- expect(result.data).toBe(undefined);
- expect(result.error).toBe(undefined);
- finished = true;
- } catch (error) {
- reject(error);
- }
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(finished).toBe(true);
- }).then(resolve, reject);
- });
-
- it("onCompleted with data", async () => {
- const query = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
-
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const data2 = { allPeople: { people: [{ name: "Han Solo" }] } };
- const mocks = [
- {
- request: { query, variables: { first: 1 } },
- result: { data: data1 },
- },
- {
- request: { query, variables: { first: 2 } },
- result: { data: data2 },
- },
- ];
-
- let count = 0;
-
- class Component extends React.Component {
- state = {
- variables: {
- first: 1,
- },
- };
-
- componentDidMount() {
- setTimeout(() => {
- this.setState({ variables: { first: 2 } });
- }, 10);
- }
-
- onCompleted(data: Data | {}) {
- if (count === 0) {
- expect(data).toEqual(data1);
- }
- if (count === 1) {
- expect(data).toEqual(data2);
- }
- count += 1;
- }
-
- render() {
- const { variables } = this.state;
- return (
-
- {() => null}
-
- );
- }
- }
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toBe(2);
- });
- });
-
- itAsync("onError with data", (resolve, reject) => {
- let finished = false;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
-
- const mocks = [
- {
- request: { query: allPeopleQuery },
- result: { data: data },
- },
- ];
-
- const onErrorFunc = (queryError: ApolloError) => {
- expect(queryError).toEqual(null);
- };
-
- const onError = jest.fn();
-
- const Component = () => (
-
- {({ loading }: any) => {
- if (!loading) {
- expect(onError).not.toHaveBeenCalled();
- finished = true;
- }
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(finished).toBe(true);
- }).then(resolve, reject);
- });
- });
-
- describe("props disallow", () => {
- it("Mutation provided as query", () => {
- const mutation = gql`
- mutation submitRepository {
- submitRepository(repoFullName: "apollographql/apollo-client") {
- createdAt
- }
- }
- `;
-
- // Prevent error from being logged in console of test.
- const errorLogger = console.error;
- console.error = () => {};
- expect(() => {
- render(
-
- {() => null}
-
- );
- }).toThrowError(
- "Running a Query requires a graphql Query, but a Mutation was used " +
- "instead."
- );
-
- console.error = errorLogger;
- });
-
- it("Subscription provided as query", () => {
- const subscription = gql`
- subscription onCommentAdded($repoFullName: String!) {
- commentAdded(repoFullName: $repoFullName) {
- id
- content
- }
- }
- `;
-
- // Prevent error from being logged in console of test.
- const errorLogger = console.error;
- console.error = () => {};
- expect(() => {
- render(
-
- {() => null}
-
- );
- }).toThrowError(
- "Running a Query requires a graphql Query, but a Subscription was " +
- "used instead."
- );
-
- console.error = errorLogger;
- });
-
- itAsync("onCompleted with error", (resolve, reject) => {
- let finished = false;
- const mockError = [
- {
- request: { query: allPeopleQuery },
- error: new Error("error occurred"),
- },
- ];
-
- const onCompleted = jest.fn();
-
- const Component = () => (
-
- {({ error }: any) => {
- if (error) {
- expect(onCompleted).not.toHaveBeenCalled();
- finished = true;
- }
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(finished).toBe(true);
- }).then(resolve, reject);
- });
-
- it("onError with error", async () => {
- let finished = false;
- const error = new Error("error occurred");
- const mockError = [
- {
- request: { query: allPeopleQuery },
- error: error,
- },
- ];
-
- const onErrorFunc = (queryError: ApolloError) => {
- expect(queryError.networkError).toEqual(error);
- finished = true;
- };
-
- const Component = () => (
-
- {() => {
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(finished).toBe(true);
- });
- });
- });
-
- describe("should update", () => {
- itAsync("if props change", (resolve, reject) => {
- const query = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
-
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const data2 = { allPeople: { people: [{ name: "Han Solo" }] } };
- const mocks = [
- {
- request: { query, variables: { first: 1 } },
- result: { data: data1 },
- },
- {
- request: { query, variables: { first: 2 } },
- result: { data: data2 },
- },
- ];
-
- let count = 0;
-
- class Component extends React.Component {
- state = {
- variables: {
- first: 1,
- },
- };
-
- render() {
- const { variables } = this.state;
-
- return (
-
- {(result: any) => {
- try {
- switch (++count) {
- case 1:
- expect(result.loading).toBe(true);
- expect(result.data).toBeUndefined();
- expect(variables).toEqual({ first: 1 });
- break;
- case 2:
- expect(result.loading).toEqual(false);
- expect(result.data).toEqual(data1);
- expect(variables).toEqual({ first: 1 });
- setTimeout(() => {
- this.setState({
- variables: {
- first: 2,
- },
- });
- });
- break;
- case 3:
- expect(result.loading).toEqual(true);
- expect(result.data).toBeUndefined();
- expect(variables).toEqual({ first: 2 });
- break;
- case 4:
- expect(result.loading).toEqual(false);
- expect(result.data).toEqual(data2);
- expect(variables).toEqual({ first: 2 });
- break;
- default:
- reject(`Too many renders (${count})`);
- }
- } catch (error) {
- reject(error);
- }
-
- return null;
- }}
-
- );
- }
- }
-
- render(
-
-
-
- );
-
- waitFor(() => expect(count).toBe(4)).then(resolve, reject);
- });
-
- itAsync("if the query changes", (resolve, reject) => {
- const query1 = allPeopleQuery;
- const query2 = gql`
- query people {
- allPeople(first: 1) {
- people {
- id
- name
- }
- }
- }
- `;
-
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const data2 = { allPeople: { people: [{ name: "Han Solo", id: "1" }] } };
- const mocks = [
- {
- request: { query: query1 },
- result: { data: data1 },
- },
- {
- request: { query: query2 },
- result: { data: data2 },
- },
- ];
-
- let count = 0;
-
- class Component extends React.Component {
- state = {
- query: query1,
- };
-
- render() {
- const { query } = this.state;
-
- return (
-
- {(result: any) => {
- if (result.loading) return null;
- try {
- switch (++count) {
- case 1:
- expect(query).toEqual(query1);
- expect(result.data).toEqual(data1);
- setTimeout(() => {
- this.setState({ query: query2 });
- });
- break;
- case 2:
- expect(query).toEqual(query2);
- expect(result.data).toEqual(data2);
- break;
- default:
- reject(`Too many renders (${count})`);
- break;
- }
- } catch (error) {
- reject(error);
- }
-
- return null;
- }}
-
- );
- }
- }
-
- render(
-
-
-
- );
-
- waitFor(() => expect(count).toBe(2)).then(resolve, reject);
- });
-
- it("with data while loading", async () => {
- const query = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
-
- const data1 = {
- allPeople: {
- people: [{ name: "Luke Skywalker" }],
- },
- };
- const data2 = {
- allPeople: { people: [{ name: "Han Solo" }] },
- };
- const mocks = [
- {
- request: { query, variables: { first: 1 } },
- result: { data: data1 },
- },
- {
- request: { query, variables: { first: 2 } },
- result: { data: data2 },
- },
- ];
-
- let count = 0;
-
- class Component extends React.Component {
- state = {
- variables: {
- first: 1,
- },
- };
-
- render() {
- const { variables } = this.state;
-
- return (
-
- {(result: any) => {
- try {
- switch (++count) {
- case 1:
- expect(result.loading).toBe(true);
- expect(result.data).toBeUndefined();
- expect(result.networkStatus).toBe(NetworkStatus.loading);
- break;
- case 2:
- expect(result.loading).toBe(false);
- expect(result.data).toEqual(data1);
- expect(result.networkStatus).toBe(NetworkStatus.ready);
- setTimeout(() => {
- this.setState({ variables: { first: 2 } });
- });
- break;
- case 3:
- expect(result.loading).toBe(true);
- expect(result.data).toBeUndefined();
- expect(result.networkStatus).toBe(
- NetworkStatus.setVariables
- );
- break;
- case 4:
- expect(result.loading).toBe(false);
- expect(result.data).toEqual(data2);
- expect(result.networkStatus).toBe(NetworkStatus.ready);
- break;
- }
- } catch (err) {
- fail(err);
- }
- return null;
- }}
-
- );
- }
- }
-
- render(
-
-
-
- );
- });
-
- itAsync(
- "should update if a manual `refetch` is triggered after a state change",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query {
- allPeople {
- people {
- name
- }
- }
- }
- `;
-
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
-
- const link = mockSingleLink(
- {
- request: { query },
- result: { data: data1 },
- },
- {
- request: { query },
- result: { data: data1 },
- },
- {
- request: { query },
- result: { data: data1 },
- }
- );
-
- const client = new ApolloClient({
- link,
- cache: new InMemoryCache({ addTypename: false }),
- });
-
- let count = 0;
-
- class SomeComponent extends React.Component {
- constructor(props: any) {
- super(props);
- this.state = {
- open: false,
- };
- this.toggle = this.toggle.bind(this);
- }
-
- toggle() {
- this.setState((prevState: any) => ({
- open: !prevState.open,
- }));
- }
-
- render() {
- const { open } = this.state as any;
- return (
-
- {(props: any) => {
- try {
- switch (count) {
- case 0:
- // Loading first response
- expect(props.loading).toBe(true);
- expect(open).toBe(false);
- break;
- case 1:
- // First response loaded, change state value
- expect(props.data).toEqual(data1);
- expect(open).toBe(false);
- setTimeout(() => {
- this.toggle();
- });
- break;
- case 2:
- // State value changed, fire a refetch
- expect(open).toBe(true);
- setTimeout(() => {
- props.refetch();
- });
- break;
- case 3:
- // Second response loading
- expect(props.loading).toBe(true);
- break;
- case 4:
- // Second response received, fire another refetch
- expect(props.data).toEqual(data1);
- setTimeout(() => {
- props.refetch();
- });
- break;
- case 5:
- // Third response loading
- expect(props.loading).toBe(true);
- break;
- case 6:
- // Third response received
- expect(props.data).toEqual(data1);
- break;
- default:
- reject("Unknown count");
- }
- count += 1;
- } catch (error) {
- reject(error);
- }
- return null;
- }}
-
- );
- }
- }
-
- render( );
-
- waitFor(() => {
- expect(count).toBe(7);
- }).then(resolve, reject);
- }
- );
- });
-
- it("should error if the query changes type to a subscription", async () => {
- let finished = false;
- const subscription = gql`
- subscription onCommentAdded($repoFullName: String!) {
- commentAdded(repoFullName: $repoFullName) {
- id
- content
- }
- }
- `;
-
- // Prevent error from showing up in console.
- const errorLog = console.error;
- console.error = () => {};
-
- class Component extends React.Component {
- state = { query: allPeopleQuery };
-
- componentDidCatch(error: any) {
- const expectedError = new Error(
- "Running a Query requires a graphql Query, but a Subscription was " +
- "used instead."
- );
- expect(error).toEqual(expectedError);
- finished = true;
- }
-
- componentDidMount() {
- setTimeout(() => {
- this.setState({
- query: subscription,
- });
- });
- }
-
- render() {
- const { query } = this.state;
- return {() => null} ;
- }
- }
-
- render(
-
-
-
- );
-
- await waitFor(
- () => {
- expect(finished).toBe(true);
- },
- { interval: 1 }
- );
- console.error = errorLog;
- });
-
- it("should be able to refetch after there was a network error", async () => {
- const query: DocumentNode = gql`
- query somethingelse {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
-
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const dataTwo = { allPeople: { people: [{ name: "Princess Leia" }] } };
- const link = mockSingleLink(
- { request: { query }, result: { data } },
- { request: { query }, error: new Error("This is an error!") },
- { request: { query }, result: { data: dataTwo }, delay: 10 }
- );
- const client = new ApolloClient({
- link,
- cache: new InMemoryCache({ addTypename: false }),
- });
-
- const noop = () => null;
-
- const AllPeopleQuery2 = Query;
-
- function Container() {
- return (
-
- {(r: any) => {
- replaceSnapshot(r);
- return null;
- }}
-
- );
- }
-
- using _disabledAct = disableActEnvironment();
- const { takeRender, replaceSnapshot, render } =
- createRenderStream();
- await render(
-
-
-
- );
-
- {
- const { snapshot } = await takeRender();
- expect(snapshot.loading).toBe(true);
- }
-
- {
- const { snapshot } = await takeRender();
- expect(snapshot.loading).toBe(false);
- expect(snapshot.data.allPeople).toEqual(data.allPeople);
- // First result is loaded, run a refetch to get the second result
- // which is an error.
- snapshot.refetch().then(() => {
- fail("Expected error value on first refetch.");
- }, noop);
- }
-
- {
- const { snapshot } = await takeRender();
- // Waiting for the second result to load
- expect(snapshot.loading).toBe(true);
- }
-
- {
- const { snapshot } = await takeRender();
- expect(snapshot.loading).toBe(false);
- expect(snapshot.error).toBeTruthy();
- // The error arrived, run a refetch to get the third result
- // which should now contain valid data.
- snapshot.refetch().catch(() => {
- fail("Expected good data on second refetch.");
- });
- }
-
- {
- const { snapshot } = await takeRender();
- expect(snapshot.loading).toBe(true);
- expect(snapshot.error).toBeFalsy();
- }
-
- {
- const { snapshot } = await takeRender();
- expect(snapshot.loading).toBe(false);
- expect(snapshot.error).toBeFalsy();
- expect(snapshot.data.allPeople).toEqual(dataTwo.allPeople);
- }
- });
-
- itAsync(
- "should not persist previous result errors when a subsequent valid result is received",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query somethingelse($variable: Boolean) {
- allPeople(first: 1, yetisArePeople: $variable) {
- people {
- name
- }
- }
- }
- `;
-
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const variableGood = { variable: true };
- const variableBad = { variable: false };
-
- const link = mockSingleLink(
- {
- request: {
- query,
- variables: variableGood,
- },
- result: {
- data,
- },
- },
- {
- request: {
- query,
- variables: variableBad,
- },
- result: {
- errors: [new Error("This is an error!")],
- },
- },
- {
- request: {
- query,
- variables: variableGood,
- },
- result: {
- data,
- },
- }
- );
-
- const client = new ApolloClient({
- link,
- cache: new InMemoryCache({ addTypename: false }),
- });
-
- let count = 0;
- const DummyComp = (props: any) => {
- try {
- switch (count++) {
- case 0:
- expect(props.loading).toBeTruthy();
- break;
- case 1:
- expect(props.data.allPeople).toBeTruthy();
- expect(props.error).toBeFalsy();
- // Change query variables to trigger bad result.
- setTimeout(() => {
- render(
-
- {(result: any) => {
- return ;
- }}
-
- );
- });
- break;
- case 2:
- expect(props.loading).toBeTruthy();
- break;
- case 3:
- // Error should be received.
- expect(props.error).toBeTruthy();
- // Change query variables to trigger a good result.
- setTimeout(() => {
- render(
-
- {(result: any) => {
- return ;
- }}
-
- );
- });
- break;
- case 4:
- // Good result should be received without any errors.
- expect(props.error).toBeFalsy();
- expect(props.data.allPeople).toBeTruthy();
- break;
- default:
- reject("Unknown count");
- }
- } catch (error) {
- reject(error);
- }
- return null;
- };
-
- render(
-
- {(result: any) => {
- return ;
- }}
-
- );
-
- waitFor(() => expect(count).toBe(5)).then(resolve, reject);
- }
- );
-
- it("should support mixing setState and onCompleted", async () => {
- const query = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
-
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const data2 = { allPeople: { people: [{ name: "Han Solo" }] } };
- const mocks = [
- {
- request: { query, variables: { first: 1 } },
- result: { data: data1 },
- },
- {
- request: { query, variables: { first: 2 } },
- result: { data: data2 },
- },
- ];
-
- let renderCount = 0;
- let onCompletedCallCount = 0;
- class Component extends React.Component {
- state = {
- variables: {
- first: 1,
- },
- };
-
- componentDidMount() {
- setTimeout(() => {
- this.setState({ variables: { first: 2 } });
- }, 10);
- }
-
- onCompleted() {
- onCompletedCallCount += 1;
- }
-
- render() {
- const { variables } = this.state;
- return (
-
- {({ loading, data }: any) => {
- switch (++renderCount) {
- case 1:
- expect(loading).toBe(true);
- expect(data).toBeUndefined();
- break;
- case 2:
- expect(loading).toBe(false);
- expect(data).toEqual(data1);
- break;
- case 3:
- expect(loading).toBe(true);
- expect(data).toBeUndefined();
- break;
- case 4:
- expect(loading).toBe(false);
- expect(data).toEqual(data2);
- setTimeout(() => {
- this.setState({ variables: { first: 1 } });
- });
- break;
- case 5:
- expect(loading).toBe(false);
- expect(data).toEqual(data1);
- break;
- default:
- console.error(`Too many renders (${renderCount})`);
- }
- return null;
- }}
-
- );
- }
- }
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(renderCount).toBe(5);
- });
- await waitFor(() => {
- expect(onCompletedCallCount).toBe(3);
- });
- });
-
- itAsync(
- "should not repeatedly call onError if setState in it",
- (resolve, reject) => {
- const mockError = [
- {
- request: { query: allPeopleQuery, variables: { first: 1 } },
- error: new Error("error occurred"),
- },
- ];
-
- let unmount: any;
- let onErrorCallCount = 0;
- class Component extends React.Component {
- state = {
- variables: {
- first: 1,
- },
- };
- onError = () => {
- onErrorCallCount += 1;
- this.setState({ causeUpdate: true });
- };
- render() {
- return (
-
- {({ loading }: any) => {
- if (!loading) {
- setTimeout(unmount);
- }
- return null;
- }}
-
- );
- }
- }
-
- unmount = render(
-
-
-
- ).unmount;
-
- waitFor(() => {
- expect(onErrorCallCount).toBe(1);
- }).then(resolve, reject);
- }
- );
-
- describe("Partial refetching", () => {
- let errorSpy!: ReturnType;
-
- beforeEach(() => {
- errorSpy = jest.spyOn(console, "error").mockImplementation(() => {});
- });
-
- afterAll(() => {
- errorSpy.mockRestore();
- });
-
- // TODO(brian): This is a terrible legacy test which is causing console
- // error calls no matter what I try and I do not want to care about it
- // anymore :)
- itAsync.skip(
- "should attempt a refetch when the query result was marked as being " +
- "partial, the returned data was reset to an empty Object by the " +
- "Apollo Client QueryManager (due to a cache miss), and the " +
- "`partialRefetch` prop is `true`",
- (resolve, reject) => {
- const allPeopleQuery: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
-
- let count = 0;
- const allPeopleData = {
- allPeople: { people: [{ name: "Luke Skywalker" }] },
- };
- const query = allPeopleQuery;
- const link = mockSingleLink(
- { request: { query }, result: { data: {} } },
- { request: { query }, result: { data: allPeopleData } }
- );
-
- const client = new ApolloClient({
- link,
- cache: new InMemoryCache(),
- });
-
- const Component = () => (
-
- {(result: any) => {
- count += 1;
- const { data, loading } = result;
- try {
- if (!loading) {
- expect(data).toEqual(allPeopleData);
- expect(errorSpy).toHaveBeenCalledTimes(1);
- expect(errorSpy.mock.calls[0][0]).toMatch("Missing field");
- }
- } catch (err) {
- reject(err);
- }
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(count).toBe(3);
- }).then(resolve, reject);
- }
- );
-
- itAsync.skip(
- "should not refetch when an empty partial is returned if the " +
- "`partialRefetch` prop is false/not set",
- (resolve, reject) => {
- let finished = false;
- const query = allPeopleQuery;
- const link = mockSingleLink({
- request: { query },
- result: { data: {} },
- });
-
- const client = new ApolloClient({
- link,
- cache: new InMemoryCache({ addTypename: false }),
- });
-
- const Component = () => (
-
- {(result: any) => {
- const { data } = result;
- expect(data).toBe(undefined);
- finished = true;
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(finished).toBe(true);
- }).then(resolve, reject);
- }
- );
- });
-
- itAsync(
- "should keep data for a `Query` component using `no-cache` when the " +
- "tree is re-rendered",
- (resolve, reject) => {
- const query1 = allPeopleQuery;
-
- const query2: DocumentNode = gql`
- query Things {
- allThings {
- thing {
- description
- }
- }
- }
- `;
-
- interface ThingData {
- allThings: {
- thing: Array<{ description: string }>;
- };
- }
-
- const allThingsData: ThingData = {
- allThings: {
- thing: [{ description: "Thing 1" }, { description: "Thing 2" }],
- },
- };
-
- const link = mockSingleLink(
- { request: { query: query1 }, result: { data: allPeopleData } },
- { request: { query: query2 }, result: { data: allThingsData } },
- { request: { query: query1 }, result: { data: allPeopleData } }
- );
-
- const client = new ApolloClient({
- link,
- cache: new InMemoryCache({ addTypename: false }),
- });
-
- let expectCount = 0;
-
- const People = () => {
- let renderCount = 0;
- return (
-
- {({ data, loading }: any) => {
- if (renderCount > 0 && !loading) {
- expect(data).toEqual(allPeopleData);
- expectCount += 1;
- }
- renderCount += 1;
- return null;
- }}
-
- );
- };
-
- const Things = () => (
-
- {({ data, loading }: any) => {
- if (!loading) {
- expect(data).toEqual(allThingsData);
- expectCount += 1;
- }
- return null;
- }}
-
- );
-
- const App = () => (
-
-
-
-
- );
-
- render( );
-
- waitFor(() => expect(expectCount).toBe(2)).then(resolve, reject);
- }
- );
-
- describe("Return partial data", () => {
- const origConsoleWarn = console.warn;
-
- beforeAll(() => {
- console.warn = () => null;
- });
-
- afterAll(() => {
- console.warn = origConsoleWarn;
- });
-
- it("should not return partial cache data when `returnPartialData` is false", async () => {
- let finished = false;
- const cache = new InMemoryCache();
- const client = new ApolloClient({
- cache,
- link: ApolloLink.empty(),
- });
-
- const fullQuery = gql`
- query {
- cars {
- make
- model
- repairs {
- date
- description
- }
- }
- }
- `;
-
- cache.writeQuery({
- query: fullQuery,
- data: {
- cars: [
- {
- __typename: "Car",
- make: "Ford",
- model: "Mustang",
- vin: "PONY123",
- repairs: [
- {
- __typename: "Repair",
- date: "2019-05-08",
- description: "Could not get after it.",
- },
- ],
- },
- ],
- },
- });
-
- const partialQuery = gql`
- query {
- cars {
- repairs {
- date
- cost
- }
- }
- }
- `;
-
- const App = () => (
-
-
- {({ data }: any) => {
- expect(data).toBeUndefined();
- finished = true;
- return null;
- }}
-
-
- );
-
- render( );
-
- await waitFor(() => {
- expect(finished).toBe(true);
- });
- });
-
- it("should return partial cache data when `returnPartialData` is true", async () => {
- let finished = false;
- const cache = new InMemoryCache();
- const client = new ApolloClient({
- cache,
- link: ApolloLink.empty(),
- });
-
- const fullQuery = gql`
- query {
- cars {
- make
- model
- repairs {
- date
- description
- }
- }
- }
- `;
-
- cache.writeQuery({
- query: fullQuery,
- data: {
- cars: [
- {
- __typename: "Car",
- make: "Ford",
- model: "Mustang",
- vin: "PONY123",
- repairs: [
- {
- __typename: "Repair",
- date: "2019-05-08",
- description: "Could not get after it.",
- },
- ],
- },
- ],
- },
- });
-
- const partialQuery = gql`
- query {
- cars {
- repairs {
- date
- cost
- }
- }
- }
- `;
-
- const App = () => (
-
-
- {({ data }: any) => {
- expect(data).toEqual({
- cars: [
- {
- __typename: "Car",
- repairs: [
- {
- __typename: "Repair",
- date: "2019-05-08",
- },
- ],
- },
- ],
- });
- finished = true;
- return null;
- }}
-
-
- );
-
- render( );
-
- await waitFor(() => {
- expect(finished).toBe(true);
- });
- });
- });
-});
diff --git a/src/react/components/__tests__/client/Subscription.test.tsx b/src/react/components/__tests__/client/Subscription.test.tsx
deleted file mode 100644
index efe15db56c0..00000000000
--- a/src/react/components/__tests__/client/Subscription.test.tsx
+++ /dev/null
@@ -1,876 +0,0 @@
-import React from "react";
-import gql from "graphql-tag";
-import { render, waitFor } from "@testing-library/react";
-
-import { ApolloClient, ApolloError } from "../../../../core";
-import { InMemoryCache as Cache } from "../../../../cache";
-import { ApolloProvider } from "../../../context";
-import { ApolloLink, DocumentNode, Operation } from "../../../../link/core";
-import { itAsync, MockSubscriptionLink } from "../../../../testing";
-import { Subscription } from "../../Subscription";
-import { spyOnConsole } from "../../../../testing/internal";
-import {
- disableActEnvironment,
- createRenderStream,
-} from "@testing-library/react-render-stream";
-
-const results = [
- "Luke Skywalker",
- "Han Solo",
- "Darth Vader",
- "Leia Skywalker",
-].map((name) => ({
- result: { data: { user: { name } } },
-}));
-
-beforeEach(() => {
- jest.useRealTimers();
-});
-
-const subscription = gql`
- subscription UserInfo {
- user {
- name
- }
- }
-`;
-
-const cache = new Cache({ addTypename: false });
-const link = new MockSubscriptionLink();
-const client = new ApolloClient({
- link,
- cache,
-});
-
-itAsync("executes the subscription", (resolve, reject) => {
- let renderCount = 0;
- const Component = () => (
-
- {(result: any) => {
- const { loading, data, error } = result;
- switch (renderCount) {
- case 0:
- expect(loading).toBe(true);
- expect(error).toBeUndefined();
- expect(data).toBeUndefined();
- break;
- case 1:
- expect(loading).toBe(false);
- expect(data).toEqual(results[0].result.data);
- break;
- case 2:
- expect(loading).toBe(false);
- expect(data).toEqual(results[1].result.data);
- break;
- case 3:
- expect(loading).toBe(false);
- expect(data).toEqual(results[2].result.data);
- break;
- case 4:
- expect(loading).toBe(false);
- expect(data).toEqual(results[3].result.data);
- break;
- default:
- }
-
- setTimeout(() => {
- renderCount <= results.length &&
- link.simulateResult(results[renderCount - 1]);
- });
-
- renderCount += 1;
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- waitFor(() => expect(renderCount).toBe(5)).then(resolve, reject);
-});
-
-it("calls onData if given", async () => {
- let count = 0;
-
- const Component = () => (
- {
- expect(opts.client).toBeInstanceOf(ApolloClient);
- const { data } = opts.data;
- expect(data).toEqual(results[count].result.data);
- count++;
- }}
- />
- );
-
- render(
-
-
-
- );
-
- const interval = setInterval(() => {
- link.simulateResult(results[count]);
- if (count >= 3) clearInterval(interval);
- }, 10);
-
- await waitFor(() => expect(count).toBe(4));
-});
-
-it("calls onSubscriptionData with deprecation warning if given", async () => {
- using consoleSpy = spyOnConsole("warn");
- let count = 0;
-
- const Component = () => (
- {
- expect(opts.client).toBeInstanceOf(ApolloClient);
- const { data } = opts.subscriptionData;
- expect(data).toEqual(results[count].result.data);
- count++;
- }}
- />
- );
-
- render(
-
-
-
- );
-
- expect(consoleSpy.warn).toHaveBeenCalledTimes(1);
- expect(consoleSpy.warn).toHaveBeenCalledWith(
- expect.stringContaining("'onSubscriptionData' is deprecated")
- );
-
- const interval = setInterval(() => {
- link.simulateResult(results[count]);
- if (count >= 3) clearInterval(interval);
- }, 10);
-
- await waitFor(() => expect(count).toBe(4));
-});
-
-it("should call onComplete if specified", async () => {
- let count = 0;
-
- let done = false;
- const Component = () => (
- {
- count++;
- }}
- onComplete={() => {
- done = true;
- }}
- />
- );
-
- render(
-
-
-
- );
-
- const interval = setInterval(() => {
- link.simulateResult(results[count], count === 3);
- if (count >= 3) clearInterval(interval);
- }, 10);
-
- await waitFor(() => expect(done).toBeTruthy());
-});
-
-it("should call onSubscriptionComplete with deprecation warning if specified", async () => {
- using consoleSpy = spyOnConsole("warn");
- let count = 0;
-
- let done = false;
- const Component = () => (
- {
- count++;
- }}
- onSubscriptionComplete={() => {
- done = true;
- }}
- />
- );
-
- render(
-
-
-
- );
-
- expect(consoleSpy.warn).toHaveBeenCalledTimes(1);
- expect(consoleSpy.warn).toHaveBeenCalledWith(
- expect.stringContaining("'onSubscriptionComplete' is deprecated")
- );
-
- const interval = setInterval(() => {
- link.simulateResult(results[count], count === 3);
- if (count >= 3) clearInterval(interval);
- }, 10);
-
- await waitFor(() => expect(done).toBeTruthy());
-});
-
-itAsync(
- "executes subscription for the variables passed in the props",
- (resolve, reject) => {
- const subscriptionWithVariables = gql`
- subscription UserInfo($name: String) {
- user(name: $name) {
- name
- }
- }
- `;
-
- const variables = { name: "Luke Skywalker" };
-
- class MockSubscriptionLinkOverride extends MockSubscriptionLink {
- request(req: Operation) {
- try {
- expect(req.variables).toEqual(variables);
- } catch (error) {
- reject(error);
- }
- return super.request(req);
- }
- }
-
- const mockLink = new MockSubscriptionLinkOverride();
-
- const mockClient = new ApolloClient({
- link: mockLink,
- cache,
- });
-
- let count = 0;
-
- const Component = () => (
-
- {(result: any) => {
- const { loading, data } = result;
-
- try {
- if (count === 0) {
- expect(loading).toBe(true);
- } else if (count === 1) {
- expect(loading).toBe(false);
- expect(data).toEqual(results[0].result.data);
- }
- } catch (error) {
- reject(error);
- }
- count++;
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- mockLink.simulateResult(results[0]);
-
- waitFor(() => expect(count).toBe(2)).then(resolve, reject);
- }
-);
-
-itAsync("does not execute if variables have not changed", (resolve, reject) => {
- const subscriptionWithVariables = gql`
- subscription UserInfo($name: String) {
- user(name: $name) {
- name
- }
- }
- `;
-
- const name = "Luke Skywalker";
-
- class MockSubscriptionLinkOverride extends MockSubscriptionLink {
- request(req: Operation) {
- try {
- expect(req.variables).toEqual({ name });
- } catch (error) {
- reject(error);
- }
- return super.request(req);
- }
- }
-
- const mockLink = new MockSubscriptionLinkOverride();
-
- const mockClient = new ApolloClient({
- link: mockLink,
- cache,
- });
-
- let count = 0;
-
- class Component extends React.Component {
- render() {
- return (
-
- {(result: any) => {
- const { loading } = result;
- try {
- if (count === 0) {
- expect(loading).toBe(true);
- } else if (count === 1) {
- expect(loading).toBe(false);
- setTimeout(() => this.forceUpdate());
- } else if (count === 2) {
- expect(loading).toBe(false);
- }
- } catch (error) {
- reject(error);
- }
- count++;
- return null;
- }}
-
- );
- }
- }
-
- render(
-
-
-
- );
-
- mockLink.simulateResult(results[0]);
-
- waitFor(() => expect(count).toBe(3)).then(resolve, reject);
-});
-
-itAsync("renders an error", (resolve, reject) => {
- const subscriptionWithVariables = gql`
- subscription UserInfo($name: String) {
- user(name: $name) {
- name
- }
- }
- `;
-
- const variables = {
- name: "Luke Skywalker",
- };
-
- const subscriptionError = {
- error: new Error("error occurred"),
- };
-
- let count = 0;
- const Component = () => (
-
- {(result: any) => {
- const { loading, data, error } = result;
- try {
- if (count === 0) {
- expect(loading).toBe(true);
- expect(error).toBeUndefined();
- } else if (count === 1) {
- expect(loading).toBe(false);
- expect(error).toEqual(
- new ApolloError({ protocolErrors: [new Error("error occurred")] })
- );
- expect(data).toBeUndefined();
- }
- } catch (error) {
- reject(error);
- }
- count++;
-
- return null;
- }}
-
- );
-
- render(
-
-
-
- );
-
- link.simulateResult(subscriptionError);
-
- waitFor(() => expect(count).toBe(2)).then(resolve, reject);
-});
-
-describe("should update", () => {
- it("if the client changes", async () => {
- const link2 = new MockSubscriptionLink();
- const client2 = new ApolloClient({
- link: link2,
- cache: new Cache({ addTypename: false }),
- });
-
- function Container() {
- return (
-
- {(r: any) => {
- replaceSnapshot(r);
- return null;
- }}
-
- );
- }
- using _disabledAct = disableActEnvironment();
- const { takeRender, replaceSnapshot, render } = createRenderStream();
- const { rerender } = await render(
-
-
-
- );
- {
- const {
- snapshot: { loading, data },
- } = await takeRender();
- expect(loading).toBeTruthy();
- expect(data).toBeUndefined();
- }
-
- link.simulateResult(results[0]);
-
- {
- const {
- snapshot: { loading, data },
- } = await takeRender();
- expect(loading).toBeFalsy();
- expect(data).toEqual(results[0].result.data);
- }
-
- await expect(takeRender).not.toRerender({ timeout: 50 });
-
- await rerender(
-
-
-
- );
-
- {
- const {
- snapshot: { loading, data },
- } = await takeRender();
- expect(loading).toBeTruthy();
- expect(data).toBeUndefined();
- }
-
- link2.simulateResult(results[1]);
-
- {
- const {
- snapshot: { loading, data },
- } = await takeRender();
- expect(loading).toBeFalsy();
- expect(data).toEqual(results[1].result.data);
- }
-
- await expect(takeRender).not.toRerender({ timeout: 50 });
- });
-
- it("if the query changes", async () => {
- const subscriptionHero = gql`
- subscription HeroInfo {
- hero {
- name
- }
- }
- `;
-
- const heroResult = {
- result: {
- data: {
- hero: {
- name: "Chewie",
- },
- },
- },
- };
-
- const userLink = new MockSubscriptionLink();
- const heroLink = new MockSubscriptionLink();
- const linkCombined = new ApolloLink((o, f) => (f ? f(o) : null)).split(
- ({ operationName }) => operationName === "HeroInfo",
- heroLink,
- userLink
- );
-
- const mockClient = new ApolloClient({
- link: linkCombined,
- cache: new Cache({ addTypename: false }),
- });
-
- function Container({ subscription }: { subscription: DocumentNode }) {
- return (
-
- {(r: any) => {
- replaceSnapshot(r);
- return null;
- }}
-
- );
- }
-
- using _disabledAct = disableActEnvironment();
- const { takeRender, replaceSnapshot, render } = createRenderStream();
- const { rerender } = await render(
- ,
- {
- wrapper: ({ children }) => (
- {children}
- ),
- }
- );
-
- {
- const {
- snapshot: { loading, data },
- } = await takeRender();
- expect(loading).toBeTruthy();
- expect(data).toBeUndefined();
- }
- userLink.simulateResult(results[0]);
-
- {
- const {
- snapshot: { loading, data },
- } = await takeRender();
- expect(loading).toBeFalsy();
- expect(data).toEqual(results[0].result.data);
- }
-
- await expect(takeRender).not.toRerender({ timeout: 50 });
-
- await rerender( );
- {
- const {
- snapshot: { loading, data },
- } = await takeRender();
- expect(loading).toBeTruthy();
- expect(data).toBeUndefined();
- }
-
- heroLink.simulateResult(heroResult);
-
- {
- const {
- snapshot: { loading, data },
- } = await takeRender();
- expect(loading).toBeFalsy();
- expect(data).toEqual(heroResult.result.data);
- }
-
- await expect(takeRender).not.toRerender({ timeout: 50 });
- });
-
- it("if the variables change", async () => {
- const subscriptionWithVariables = gql`
- subscription UserInfo($name: String) {
- user(name: $name) {
- name
- }
- }
- `;
-
- const variablesLuke = { name: "Luke Skywalker" };
- const variablesHan = { name: "Han Solo" };
-
- const dataLuke = {
- user: {
- name: "Luke Skywalker",
- },
- };
-
- const dataHan = {
- user: {
- name: "Han Solo",
- },
- };
-
- const mockLink = new MockSubscriptionLink();
-
- const mockClient = new ApolloClient({
- link: mockLink,
- cache,
- });
-
- function Container({ variables }: { variables: any }) {
- return (
-
- {(r: any) => {
- replaceSnapshot(r);
- return null;
- }}
-
- );
- }
- using _disabledAct = disableActEnvironment();
- const { takeRender, render, replaceSnapshot } = createRenderStream();
- const { rerender } = await render( , {
- wrapper: ({ children }) => (
- {children}
- ),
- });
-
- {
- const {
- snapshot: { loading, data },
- } = await takeRender();
- expect(loading).toBeTruthy();
- expect(data).toBeUndefined();
- }
- mockLink.simulateResult({ result: { data: dataLuke } });
-
- {
- const {
- snapshot: { loading, data },
- } = await takeRender();
- expect(loading).toBeFalsy();
- expect(data).toEqual(dataLuke);
- }
-
- await expect(takeRender).not.toRerender({ timeout: 50 });
-
- await rerender( );
-
- {
- const {
- snapshot: { loading, data },
- } = await takeRender();
- expect(loading).toBeTruthy();
- expect(data).toBeUndefined();
- }
- mockLink.simulateResult({
- result: { data: dataHan },
- });
- {
- const {
- snapshot: { loading, data },
- } = await takeRender();
- expect(loading).toBeFalsy();
- expect(data).toEqual(dataHan);
- }
-
- await expect(takeRender).not.toRerender({ timeout: 50 });
- });
-});
-
-describe("should not update", () => {
- const variablesLuke = { name: "Luke Skywalker" };
- const variablesHan = { name: "Han Solo" };
-
- const dataLuke = {
- user: {
- name: "Luke Skywalker",
- },
- };
-
- const dataHan = {
- user: {
- name: "Han Solo",
- },
- };
-
- class MockSubscriptionLinkOverride extends MockSubscriptionLink {
- variables: any;
- request(req: Operation) {
- this.variables = req.variables;
- return super.request(req);
- }
-
- simulateResult() {
- if (this.variables.name === "Luke Skywalker") {
- return super.simulateResult({
- result: {
- data: dataLuke,
- },
- });
- } else if (this.variables.name === "Han Solo") {
- return super.simulateResult({
- result: {
- data: dataHan,
- },
- });
- }
- }
- }
-
- itAsync("if shouldResubscribe is false", (resolve, reject) => {
- const subscriptionWithVariables = gql`
- subscription UserInfo($name: String) {
- user(name: $name) {
- name
- }
- }
- `;
-
- const mockLink = new MockSubscriptionLinkOverride();
-
- const mockClient = new ApolloClient({
- link: mockLink,
- cache,
- });
-
- let count = 0;
-
- class Component extends React.Component {
- state = {
- variables: variablesLuke,
- };
-
- render() {
- return (
-
- {(result: any) => {
- const { loading, data } = result;
- try {
- if (count === 0) {
- expect(loading).toBeTruthy();
- expect(data).toBeUndefined();
- } else if (count === 1) {
- expect(loading).toBeFalsy();
- expect(data).toEqual(dataLuke);
- setTimeout(() => {
- this.setState(
- {
- variables: variablesHan,
- },
- () => {
- mockLink.simulateResult();
- }
- );
- });
- } else if (count === 2) {
- expect(loading).toBeFalsy();
- expect(data).toEqual(dataLuke);
- }
- } catch (error) {
- reject(error);
- }
-
- count++;
- return null;
- }}
-
- );
- }
- }
-
- render(
-
-
-
- );
-
- mockLink.simulateResult();
-
- waitFor(() => expect(count).toBe(4)).then(resolve, reject);
- });
-
- itAsync("if shouldResubscribe returns false", (resolve, reject) => {
- const subscriptionWithVariables = gql`
- subscription UserInfo($name: String) {
- user(name: $name) {
- name
- }
- }
- `;
-
- const mockLink = new MockSubscriptionLinkOverride();
-
- const mockClient = new ApolloClient({
- link: mockLink,
- cache,
- });
-
- let count = 0;
-
- class Component extends React.Component {
- state = {
- variables: variablesLuke,
- };
-
- render() {
- return (
- false}
- >
- {(result: any) => {
- const { loading, data } = result;
- try {
- if (count === 0) {
- expect(loading).toBeTruthy();
- expect(data).toBeUndefined();
- } else if (count === 1) {
- expect(loading).toBeFalsy();
- expect(data).toEqual(dataLuke);
- setTimeout(() => {
- this.setState(
- {
- variables: variablesHan,
- },
- () => {
- mockLink.simulateResult();
- }
- );
- });
- } else if (count === 2) {
- expect(loading).toBeFalsy();
- expect(data).toEqual(dataLuke);
- }
- } catch (error) {
- reject(error);
- }
-
- count++;
- return null;
- }}
-
- );
- }
- }
-
- render(
-
-
-
- );
-
- mockLink.simulateResult();
-
- waitFor(() => expect(count).toBe(4)).then(resolve, reject);
- });
-});
diff --git a/src/react/components/__tests__/ssr/getDataFromTree.test.tsx b/src/react/components/__tests__/ssr/getDataFromTree.test.tsx
deleted file mode 100644
index 326bdc6828c..00000000000
--- a/src/react/components/__tests__/ssr/getDataFromTree.test.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-/** @jest-environment node */
-import React from "react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import { ApolloClient, TypedDocumentNode } from "../../../../core";
-import { InMemoryCache as Cache } from "../../../../cache";
-import {
- ApolloProvider,
- getApolloContext,
- ApolloContextValue,
-} from "../../../context";
-import { getDataFromTree } from "../../../ssr";
-import { itAsync, mockSingleLink } from "../../../../testing";
-import { Query } from "../../Query";
-
-describe("SSR", () => {
- describe("`getDataFromTree`", () => {
- it("should support passing a root context", () => {
- const apolloContext = getApolloContext() as unknown as React.Context<
- ApolloContextValue & { text: string }
- >;
- class Consumer extends React.Component {
- static contextType = apolloContext;
- declare context: React.ContextType;
-
- render() {
- return {this.context.text}
;
- }
- }
-
- return getDataFromTree( , {
- text: "oyez",
- }).then((html) => {
- expect(html).toEqual("oyez
");
- });
- });
-
- it("should run through all of the queries (also defined via Query component) that want SSR", () => {
- const query: TypedDocumentNode = gql`
- {
- currentUser {
- firstName
- }
- }
- `;
- const data1 = { currentUser: { firstName: "James" } };
- const link = mockSingleLink({
- request: { query },
- result: { data: data1 },
- delay: 50,
- });
- const apolloClient = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Data {
- currentUser?: {
- firstName: string;
- };
- }
-
- const WrappedElement = () => (
-
- {({ data, loading }) => (
-
- {loading || !data ? "loading" : data.currentUser!.firstName}
-
- )}
-
- );
-
- const app = (
-
-
-
- );
-
- return getDataFromTree(app).then((markup) => {
- expect(markup).toMatch(/James/);
- });
- });
-
- itAsync(
- 'should pass any GraphQL errors in props along with data during a SSR when errorPolicy="all"',
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople {
- people {
- name
- }
- }
- }
- `;
- const link = mockSingleLink({
- request: { query },
- result: {
- data: {
- allPeople: {
- people: null,
- },
- },
- errors: [new Error("this is an error")],
- },
- });
-
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const app = (
-
-
- {({ loading, data, error }: any) => {
- if (!loading) {
- expect(data).toMatchObject({ allPeople: { people: null } });
- expect(error).toBeDefined();
- expect(error.graphQLErrors[0].message).toEqual(
- "this is an error"
- );
- resolve();
- }
- return null;
- }}
-
-
- );
-
- getDataFromTree(app);
- }
- );
- });
-});
diff --git a/src/react/components/__tests__/ssr/server.test.tsx b/src/react/components/__tests__/ssr/server.test.tsx
deleted file mode 100644
index 7d50399515d..00000000000
--- a/src/react/components/__tests__/ssr/server.test.tsx
+++ /dev/null
@@ -1,207 +0,0 @@
-/** @jest-environment node */
-import React from "react";
-import {
- print,
- graphql as execute,
- GraphQLSchema,
- GraphQLObjectType,
- GraphQLList,
- GraphQLString,
- GraphQLID,
-} from "graphql";
-import gql from "graphql-tag";
-
-import { ApolloClient } from "../../../../core";
-import { InMemoryCache as Cache } from "../../../../cache";
-import { ApolloProvider } from "../../../context";
-import { ApolloLink } from "../../../../link/core";
-import { Observable } from "../../../../utilities";
-import { renderToStringWithData } from "../../../ssr";
-import { Query } from "../../Query";
-
-const planetMap = new Map([["Planet:1", { id: "Planet:1", name: "Tatooine" }]]);
-
-const shipMap = new Map([
- [
- "Ship:2",
- {
- id: "Ship:2",
- name: "CR90 corvette",
- films: ["Film:4", "Film:6", "Film:3"],
- },
- ],
- [
- "Ship:3",
- {
- id: "Ship:3",
- name: "Star Destroyer",
- films: ["Film:4", "Film:5", "Film:6"],
- },
- ],
-]);
-
-const filmMap = new Map([
- ["Film:3", { id: "Film:3", title: "Revenge of the Sith" }],
- ["Film:4", { id: "Film:4", title: "A New Hope" }],
- ["Film:5", { id: "Film:5", title: "the Empire Strikes Back" }],
- ["Film:6", { id: "Film:6", title: "Return of the Jedi" }],
-]);
-
-const PlanetType = new GraphQLObjectType({
- name: "Planet",
- fields: {
- id: { type: GraphQLID },
- name: { type: GraphQLString },
- },
-});
-
-const FilmType = new GraphQLObjectType({
- name: "Film",
- fields: {
- id: { type: GraphQLID },
- title: { type: GraphQLString },
- },
-});
-
-const ShipType = new GraphQLObjectType({
- name: "Ship",
- fields: {
- id: { type: GraphQLID },
- name: { type: GraphQLString },
- films: {
- type: new GraphQLList(FilmType),
- resolve: ({ films }) => films.map((id: string) => filmMap.get(id)),
- },
- },
-});
-
-const QueryType = new GraphQLObjectType({
- name: "Query",
- fields: {
- allPlanets: {
- type: new GraphQLList(PlanetType),
- resolve: () => Array.from(planetMap.values()),
- },
- allShips: {
- type: new GraphQLList(ShipType),
- resolve: () => Array.from(shipMap.values()),
- },
- ship: {
- type: ShipType,
- args: { id: { type: GraphQLID } },
- resolve: (_, { id }) => shipMap.get(id),
- },
- film: {
- type: FilmType,
- args: { id: { type: GraphQLID } },
- resolve: (_, { id }) => filmMap.get(id),
- },
- },
-});
-
-const Schema = new GraphQLSchema({ query: QueryType });
-
-describe("SSR", () => {
- it("should work with React.createContext", async () => {
- let defaultValue = "default";
- let Context = React.createContext(defaultValue);
- let providerValue = "provider";
- expect(
- await renderToStringWithData(
-
-
-
- {(val) => {
- expect(val).toBe(defaultValue);
- return val;
- }}
-
-
- )
- ).toBe(defaultValue);
- expect(
- await renderToStringWithData(
-
-
- {(val) => {
- expect(val).toBe(providerValue);
- return val;
- }}
-
-
- )
- ).toBe(providerValue);
- expect(
- await renderToStringWithData(
-
- {(val) => {
- expect(val).toBe(defaultValue);
- return val;
- }}
-
- )
- ).toBe(defaultValue);
- let ContextForUndefined = React.createContext(defaultValue);
-
- expect(
- await renderToStringWithData(
-
-
- {(val) => {
- expect(val).toBeUndefined();
- return val === undefined ? "works" : "broken";
- }}
-
-
- )
- ).toBe("works");
-
- const apolloClient = new ApolloClient({
- link: new ApolloLink((config) => {
- return new Observable((observer) => {
- execute({
- schema: Schema,
- source: print(config.query),
- variableValues: config.variables,
- operationName: config.operationName,
- })
- .then((result) => {
- observer.next(result);
- observer.complete();
- })
- .catch((e) => {
- observer.error(e);
- });
- });
- }),
- cache: new Cache(),
- });
-
- expect(
- await renderToStringWithData(
-
-
-
- {() => (
-
- {(val) => {
- expect(val).toBe(providerValue);
- return val;
- }}
-
- )}
-
-
-
- )
- ).toBe(providerValue);
- });
-});
diff --git a/src/react/components/index.ts b/src/react/components/index.ts
deleted file mode 100644
index 5aa7ebb8a2e..00000000000
--- a/src/react/components/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export { Query } from "./Query.js";
-export { Mutation } from "./Mutation.js";
-export { Subscription } from "./Subscription.js";
-
-export * from "./types.js";
diff --git a/src/react/components/types.ts b/src/react/components/types.ts
deleted file mode 100644
index a742b905ac6..00000000000
--- a/src/react/components/types.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { DocumentNode } from "graphql";
-import type { TypedDocumentNode } from "@graphql-typed-document-node/core";
-
-import type * as ReactTypes from "react";
-
-import type {
- OperationVariables,
- DefaultContext,
- ApolloCache,
-} from "../../core/index.js";
-import type {
- QueryFunctionOptions,
- QueryResult,
- BaseMutationOptions,
- MutationFunction,
- MutationResult,
- BaseSubscriptionOptions,
- SubscriptionResult,
-} from "../types/types.js";
-
-export interface QueryComponentOptions<
- TData = any,
- TVariables extends OperationVariables = OperationVariables,
-> extends QueryFunctionOptions {
- children: (
- result: QueryResult
- ) => ReactTypes.JSX.Element | null;
- query: DocumentNode | TypedDocumentNode;
-}
-
-export interface MutationComponentOptions<
- TData = any,
- TVariables = OperationVariables,
- TContext = DefaultContext,
- TCache extends ApolloCache = ApolloCache,
-> extends BaseMutationOptions {
- mutation: DocumentNode | TypedDocumentNode;
- children: (
- mutateFunction: MutationFunction,
- result: MutationResult
- ) => ReactTypes.JSX.Element | null;
-}
-
-export interface SubscriptionComponentOptions<
- TData = any,
- TVariables extends OperationVariables = OperationVariables,
-> extends BaseSubscriptionOptions {
- /** {@inheritDoc @apollo/client!SubscriptionOptionsDocumentation#query:member} */
- subscription: DocumentNode | TypedDocumentNode;
- children?:
- | null
- | ((result: SubscriptionResult) => ReactTypes.JSX.Element | null);
-}
diff --git a/src/react/hoc/__tests__/client-option.test.tsx b/src/react/hoc/__tests__/client-option.test.tsx
deleted file mode 100644
index b6efd611d09..00000000000
--- a/src/react/hoc/__tests__/client-option.test.tsx
+++ /dev/null
@@ -1,217 +0,0 @@
-import React from "react";
-import { render, waitFor } from "@testing-library/react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import { ApolloClient } from "../../../core";
-import { ApolloProvider } from "../../context";
-import { InMemoryCache as Cache } from "../../../cache";
-import { itAsync, mockSingleLink } from "../../../testing";
-import { graphql } from "../graphql";
-import { ChildProps } from "../types";
-
-describe("client option", () => {
- it("renders with client from options", () => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
-
- type Data = typeof data;
-
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
- const config = {
- options: {
- client,
- },
- };
- const ContainerWithData = graphql<{}, Data>(query, config)(() => null);
- const { unmount } = render(
-
-
-
- );
- unmount();
- });
-
- itAsync("doesnt require a recycler", (resolve, reject) => {
- const query = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data;
-
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
- const config = {
- options: {
- client,
- },
- };
- let renderCount = 0;
- const ContainerWithData = graphql<{}, Data>(
- query,
- config
- )(() => {
- renderCount += 1;
- return null;
- });
- render( );
-
- waitFor(() => {
- expect(renderCount).toBe(2);
- }).then(resolve, reject);
- });
-
- itAsync(
- "ignores client from context if client from options is present",
- (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const dataProvider = {
- allPeople: { people: [{ name: "Leia Organa Solo" }] },
- };
-
- type Data = typeof dataProvider;
- const linkProvider = mockSingleLink({
- request: { query },
- result: { data: dataProvider },
- });
- const clientProvider = new ApolloClient({
- link: linkProvider,
- cache: new Cache({ addTypename: false }),
- });
- const dataOptions = {
- allPeople: { people: [{ name: "Luke Skywalker" }] },
- };
- const linkOptions = mockSingleLink({
- request: { query },
- result: { data: dataOptions },
- });
- const clientOptions = new ApolloClient({
- link: linkOptions,
- cache: new Cache({ addTypename: false }),
- });
-
- const config = {
- options: {
- client: clientOptions,
- },
- };
-
- class Container extends React.Component> {
- componentDidUpdate() {
- const { data } = this.props;
- expect(data!.loading).toBeFalsy(); // first data
- expect(data!.allPeople).toEqual({
- people: [{ name: "Luke Skywalker" }],
- });
- done = true;
- }
- render() {
- return null;
- }
- }
- const ContainerWithData = graphql<{}, Data>(query, config)(Container);
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- }
- );
-
- itAsync("exposes refetch as part of the props api", (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
- const variables = { first: 1 };
- type Variables = typeof variables;
-
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data1;
-
- const link = mockSingleLink({
- request: { query, variables },
- result: { data: data1 },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql(query)(
- class extends React.Component> {
- componentDidUpdate() {
- const { data } = this.props;
- expect(data!.loading).toBeFalsy(); // first data
- done = true;
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- });
-});
diff --git a/src/react/hoc/__tests__/fragments.test.tsx b/src/react/hoc/__tests__/fragments.test.tsx
deleted file mode 100644
index 741e5e2f8f7..00000000000
--- a/src/react/hoc/__tests__/fragments.test.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-import React from "react";
-import { render, waitFor } from "@testing-library/react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import { ApolloClient } from "../../../core";
-import { ApolloProvider } from "../../context";
-import { InMemoryCache as Cache } from "../../../cache";
-import { itAsync, mockSingleLink } from "../../../testing";
-import { graphql } from "../graphql";
-import { ChildProps } from "../types";
-
-describe("fragments", () => {
- // XXX in a later version, we should support this for composition
- it("throws if you only pass a fragment", () => {
- const query: DocumentNode = gql`
- fragment Failure on PeopleConnection {
- people {
- name
- }
- }
- `;
- const expectedData = {
- allPeople: { people: [{ name: "Luke Skywalker" }] },
- };
- type Data = typeof expectedData;
-
- const link = mockSingleLink({
- request: { query },
- result: { data: expectedData },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- try {
- const Container = graphql<{}, Data>(query)(
- class extends React.Component> {
- componentDidUpdate() {
- const { props } = this;
- expect(props.data!.loading).toBeFalsy();
- expect(props.data!.allPeople).toEqual(expectedData.allPeople);
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
- throw new Error();
- } catch (e) {
- expect((e as Error).name).toMatch(/Invariant Violation/);
- }
- });
-
- itAsync(
- "correctly fetches a query with inline fragments",
- (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- __typename
- ...person
- }
- }
-
- fragment person on PeopleConnection {
- people {
- name
- }
- }
- `;
- const data = {
- allPeople: {
- __typename: "PeopleConnection",
- people: [{ name: "Luke Skywalker" }],
- },
- };
-
- type Data = typeof data;
-
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql<{}, Data>(query)(
- class extends React.Component> {
- componentDidUpdate() {
- expect(this.props.data!.loading).toBeFalsy();
- expect(this.props.data!.allPeople).toEqual(data.allPeople);
- done = true;
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- }
- );
-});
diff --git a/src/react/hoc/__tests__/mutations/index.test.tsx b/src/react/hoc/__tests__/mutations/index.test.tsx
deleted file mode 100644
index 7f1ba109205..00000000000
--- a/src/react/hoc/__tests__/mutations/index.test.tsx
+++ /dev/null
@@ -1,291 +0,0 @@
-import React from "react";
-import { render } from "@testing-library/react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import { ApolloClient } from "../../../../core";
-import { createMockClient, itAsync, MockedProvider } from "../../../../testing";
-import { NormalizedCacheObject } from "../../../../cache";
-import { ApolloProvider } from "../../../context";
-import { graphql } from "../../graphql";
-import { ChildProps } from "../../types";
-
-const query: DocumentNode = gql`
- mutation addPerson {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
-`;
-
-interface Data {
- allPeople: {
- people: { name: string }[];
- };
-}
-
-interface Variables {
- name: string;
-}
-
-const expectedData = {
- allPeople: { people: [{ name: "Luke Skywalker" }] },
-};
-
-describe("graphql(mutation)", () => {
- let error: typeof console.error;
- let client: ApolloClient;
- beforeEach(() => {
- error = console.error;
- console.error = jest.fn(() => {});
- client = createMockClient(expectedData, query);
- });
-
- afterEach(() => {
- console.error = error;
- });
-
- it("binds a mutation to props", () => {
- const ContainerWithData = graphql(query)(({ mutate, result }) => {
- expect(mutate).toBeTruthy();
- expect(result).toBeTruthy();
- expect(typeof mutate).toBe("function");
- expect(typeof result).toBe("object");
- return null;
- });
-
- render(
-
-
-
- );
- });
-
- it("binds a mutation result to props", () => {
- type InjectedProps = {
- result: any;
- };
-
- const ContainerWithData = graphql<{}, Data, Variables, InjectedProps>(
- query
- )(({ result }) => {
- const { loading, error } = result;
- expect(result).toBeTruthy();
- expect(typeof loading).toBe("boolean");
- expect(error).toBeFalsy();
-
- return null;
- });
-
- render(
-
-
-
- );
- });
-
- it("binds a mutation to props with a custom name", () => {
- interface Props {}
-
- type InjectedProps = {
- customMutation: any;
- customMutationResult: any;
- };
-
- const ContainerWithData = graphql(
- query,
- { name: "customMutation" }
- )(({ customMutation, customMutationResult }) => {
- expect(customMutation).toBeTruthy();
- expect(customMutationResult).toBeTruthy();
- expect(typeof customMutation).toBe("function");
- expect(typeof customMutationResult).toBe("object");
- return null;
- });
-
- render(
-
-
-
- );
- });
-
- it("binds a mutation to custom props", () => {
- interface Props {
- methodName: string;
- }
- type InjectedProps = {
- [name: string]: (name: string) => void;
- };
- const ContainerWithData = graphql(
- query,
- {
- props: ({ ownProps, mutate: addPerson }) => ({
- [ownProps.methodName]: (name: string) =>
- addPerson!({ variables: { name } }),
- }),
- }
- )(({ myInjectedMutationMethod }) => {
- expect(myInjectedMutationMethod).toBeTruthy();
- expect(typeof myInjectedMutationMethod).toBe("function");
- return null;
- });
-
- render(
-
-
-
- );
- });
-
- itAsync("does not swallow children errors", (resolve, reject) => {
- let bar: any;
- const ContainerWithData = graphql(query)(() => {
- bar(); // this will throw
- return null;
- });
-
- class ErrorBoundary extends React.Component {
- componentDidCatch(e: Error) {
- expect(e.name).toMatch(/TypeError/);
- expect(e.message).toMatch(/bar is not a function/);
- resolve();
- }
-
- render() {
- // eslint-disable-next-line testing-library/no-node-access
- return this.props.children;
- }
- }
-
- render(
-
-
-
-
-
- );
- });
-
- itAsync("can execute a mutation", (resolve, reject) => {
- const Container = graphql(query)(
- class extends React.Component {
- componentDidMount() {
- this.props.mutate!().then((result) => {
- expect(result && result.data).toEqual(expectedData);
- resolve();
- });
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
- });
-
- itAsync(
- "can execute a mutation with variables from props",
- (resolve, reject) => {
- const queryWithVariables = gql`
- mutation addPerson($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
- client = createMockClient(expectedData, queryWithVariables, {
- first: 1,
- });
-
- interface Props {
- first: number;
- }
-
- const Container = graphql(queryWithVariables)(
- class extends React.Component> {
- componentDidMount() {
- this.props.mutate!().then((result) => {
- expect(result && result.data).toEqual(expectedData);
- resolve();
- });
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
- }
- );
-
- itAsync(
- "can execute a mutation with variables from BOTH options and arguments",
- (resolve, reject) => {
- const queryWithVariables = gql`
- mutation addPerson($first: Int!, $second: Int!) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
-
- const mocks = [
- {
- request: {
- query: queryWithVariables,
- variables: {
- first: 1,
- second: 2,
- },
- },
- result: { data: expectedData },
- },
- ];
-
- interface Props {}
-
- const Container = graphql(queryWithVariables, {
- options: () => ({
- variables: { first: 1 },
- }),
- })(
- class extends React.Component> {
- componentDidMount() {
- this.props.mutate!({
- variables: { second: 2 },
- }).then((result) => {
- expect(result && result.data).toEqual(expectedData);
- resolve();
- });
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
- }
- );
-});
diff --git a/src/react/hoc/__tests__/mutations/lifecycle.test.tsx b/src/react/hoc/__tests__/mutations/lifecycle.test.tsx
deleted file mode 100644
index f6ff711e48d..00000000000
--- a/src/react/hoc/__tests__/mutations/lifecycle.test.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-import React from "react";
-import { render } from "@testing-library/react";
-import gql from "graphql-tag";
-
-import { ApolloProvider } from "../../../context/ApolloProvider";
-import { itAsync, createMockClient } from "../../../../testing/core";
-import { graphql } from "../../graphql";
-import { ChildProps } from "../../types";
-
-const query = gql`
- mutation addPerson($id: Int) {
- allPeople(id: $id) {
- people {
- name
- }
- }
- }
-`;
-const expectedData = {
- allPeople: { people: [{ name: "Luke Skywalker" }] },
-};
-
-describe("graphql(mutation) lifecycle", () => {
- itAsync(
- "allows falsy values in the mapped variables from props",
- (resolve, reject) => {
- const client = createMockClient(expectedData, query, { id: null });
-
- interface Props {
- id: string | null;
- }
-
- const Container = graphql(query)(
- class extends React.Component> {
- componentDidMount() {
- this.props.mutate!().then((result) => {
- expect(result && result.data).toEqual(expectedData);
- resolve();
- });
- }
-
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
- }
- );
-
- it("errors if the passed props don't contain the needed variables", () => {
- const client = createMockClient(expectedData, query, { first: 1 });
- interface Props {
- frst: number;
- }
- const Container = graphql(query)(() => null);
- try {
- render(
-
-
-
- );
- } catch (e) {
- expect(e).toMatch(/Invariant Violation: The operation 'addPerson'/);
- }
- });
-
- itAsync(
- "rebuilds the mutation on prop change when using `options`",
- (resolve, reject) => {
- const client = createMockClient(expectedData, query, {
- id: 2,
- });
-
- interface Props {
- listId: number;
- }
-
- function options(props: Props) {
- return {
- variables: {
- id: props.listId,
- },
- };
- }
-
- class Container extends React.Component> {
- render() {
- if (this.props.listId !== 2) return null;
- setTimeout(() => {
- this.props.mutate!().then(() => resolve());
- });
- return null;
- }
- }
-
- const ContainerWithMutate = graphql(query, { options })(Container);
-
- class ChangingProps extends React.Component<{}, { listId: number }> {
- state = { listId: 1 };
-
- componentDidMount() {
- setTimeout(() => this.setState({ listId: 2 }), 50);
- }
-
- render() {
- return ;
- }
- }
-
- render(
-
-
-
- );
- }
- );
-
- itAsync("can execute a mutation with custom variables", (resolve, reject) => {
- const client = createMockClient(expectedData, query, { id: 1 });
- interface Variables {
- id: number;
- }
-
- const Container = graphql<{}, {}, Variables>(query)(
- class extends React.Component> {
- componentDidMount() {
- this.props.mutate!({ variables: { id: 1 } }).then((result) => {
- expect(result && result.data).toEqual(expectedData);
- resolve();
- });
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
- });
-});
diff --git a/src/react/hoc/__tests__/mutations/queries.test.tsx b/src/react/hoc/__tests__/mutations/queries.test.tsx
deleted file mode 100644
index 2144a974e2a..00000000000
--- a/src/react/hoc/__tests__/mutations/queries.test.tsx
+++ /dev/null
@@ -1,419 +0,0 @@
-import React from "react";
-import { act, render, waitFor } from "@testing-library/react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import {
- ApolloClient,
- MutationUpdaterFunction,
- ApolloCache,
-} from "../../../../core";
-import { ApolloProvider } from "../../../context";
-import { InMemoryCache as Cache } from "../../../../cache";
-import { itAsync, createMockClient, mockSingleLink } from "../../../../testing";
-import { graphql } from "../../graphql";
-import { ChildProps } from "../../types";
-
-describe("graphql(mutation) query integration", () => {
- itAsync(
- "allows for passing optimisticResponse for a mutation",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- mutation createTodo {
- createTodo {
- id
- text
- completed
- __typename
- }
- __typename
- }
- `;
-
- const data = {
- __typename: "Mutation",
- createTodo: {
- __typename: "Todo",
- id: "99",
- text: "This one was created with a mutation.",
- completed: true,
- },
- };
-
- type Data = typeof data;
-
- let mutateFired = false;
- const client = createMockClient(data, query);
- const Container = graphql<{}, Data>(query)(
- class extends React.Component> {
- componentDidMount() {
- const optimisticResponse = {
- __typename: "Mutation",
- createTodo: {
- __typename: "Todo",
- id: "99",
- text: "Optimistically generated",
- completed: true,
- },
- };
-
- this.props.mutate!({ optimisticResponse }).then((result) => {
- expect(result && result.data).toEqual(data);
- mutateFired = true;
- });
-
- const dataInStore = client.cache.extract(true);
- expect(dataInStore["Todo:99"]).toEqual(
- optimisticResponse.createTodo
- );
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(mutateFired).toBeTruthy();
- }).then(resolve, reject);
- }
- );
-
- itAsync("allows for updating queries from a mutation", (resolve, reject) => {
- const query: DocumentNode = gql`
- query todos {
- todo_list {
- id
- title
- tasks {
- id
- text
- completed
- }
- }
- }
- `;
-
- const mutation: DocumentNode = gql`
- mutation createTodo {
- createTodo {
- id
- text
- completed
- }
- }
- `;
-
- const mutationData = {
- createTodo: {
- id: "99",
- text: "This one was created with a mutation.",
- completed: true,
- },
- };
-
- type MutationData = typeof mutationData;
-
- const optimisticResponse = {
- createTodo: {
- id: "99",
- text: "Optimistically generated",
- completed: true,
- },
- };
- interface QueryData {
- todo_list: {
- id: string;
- title: string;
- tasks: { id: string; text: string; completed: boolean }[];
- };
- }
-
- const update: MutationUpdaterFunction<
- MutationData,
- Record,
- Record,
- ApolloCache
- > = (proxy, result) => {
- const data = JSON.parse(
- JSON.stringify(proxy.readQuery({ query }))
- );
- data.todo_list.tasks.push(result.data!.createTodo); // update value
- proxy.writeQuery({ query, data }); // write to cache
- };
-
- const expectedData = {
- todo_list: { id: "123", title: "how to apollo", tasks: [] },
- };
-
- const link = mockSingleLink(
- {
- request: { query },
- result: { data: expectedData },
- },
- { request: { query: mutation }, result: { data: mutationData } }
- );
- const cache = new Cache({ addTypename: false });
- const client = new ApolloClient({ link, cache });
-
- const withQuery = graphql<{}, QueryData>(query);
-
- type WithQueryChildProps = ChildProps<{}, QueryData>;
- const withMutation = graphql(mutation, {
- options: () => ({ optimisticResponse, update }),
- });
-
- let renderCount = 0;
-
- type ContainerProps = ChildProps;
- class Container extends React.Component {
- render() {
- if (!this.props.data || !this.props.data.todo_list) return null;
- if (!this.props.data.todo_list.tasks.length) {
- act(() => {
- this.props.mutate!().then((result) => {
- expect(result && result.data).toEqual(mutationData);
- });
- });
- return null;
- }
-
- switch (++renderCount) {
- case 1:
- expect(this.props.data.todo_list.tasks).toEqual([
- optimisticResponse.createTodo,
- ]);
- break;
- case 2:
- expect(this.props.data.todo_list.tasks).toEqual([
- mutationData.createTodo,
- ]);
- break;
- default:
- reject(`too many renders (${renderCount})`);
- }
-
- return null;
- }
- }
-
- const ContainerWithData = withQuery(withMutation(Container));
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(renderCount).toBe(2);
- }).then(resolve, reject);
- });
-
- itAsync(
- "allows for updating queries from a mutation automatically",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query getMini($id: ID!) {
- mini(id: $id) {
- __typename
- id
- cover(maxWidth: 600, maxHeight: 400)
- }
- }
- `;
-
- const queryData = {
- mini: {
- id: 1,
- __typename: "Mini",
- cover: "image1",
- },
- };
-
- type Data = typeof queryData;
-
- const variables = { id: 1 };
-
- type Variables = typeof variables;
-
- const mutation: DocumentNode = gql`
- mutation ($signature: String!) {
- mini: submitMiniCoverS3DirectUpload(signature: $signature) {
- __typename
- id
- cover(maxWidth: 600, maxHeight: 400)
- }
- }
- `;
-
- const mutationData = {
- mini: {
- id: 1,
- cover: "image2",
- __typename: "Mini",
- },
- };
-
- type MutationData = typeof mutationData;
-
- interface MutationVariables {
- signature: string;
- }
-
- const link = mockSingleLink(
- { request: { query, variables }, result: { data: queryData } },
- {
- request: { query: mutation, variables: { signature: "1233" } },
- result: { data: mutationData },
- }
- );
- const cache = new Cache({ addTypename: false });
- const client = new ApolloClient({ link, cache });
-
- class Boundary extends React.Component {
- componentDidCatch(e: any) {
- reject(e);
- }
- render() {
- // eslint-disable-next-line testing-library/no-node-access
- return this.props.children;
- }
- }
-
- let count = 0;
- const MutationContainer = graphql(
- mutation
- )(
- class extends React.Component<
- ChildProps
- > {
- render() {
- if (count === 1) {
- setTimeout(() => {
- this.props.mutate!()
- .then((result) => {
- expect(result && result.data).toEqual(mutationData);
- })
- .catch(reject);
- });
- }
- return null;
- }
- }
- );
-
- const Container = graphql(query)(
- class extends React.Component> {
- render() {
- if (count === 1) {
- expect(this.props.data!.mini).toEqual(queryData.mini);
- }
- if (count === 2) {
- expect(this.props.data!.mini).toEqual(mutationData.mini);
- }
- count++;
-
- return (
-
- );
- }
- }
- );
-
- render(
-
-
-
-
-
- );
-
- waitFor(() => {
- expect(count).toBe(3);
- }).then(resolve, reject);
- }
- );
-
- it("should be able to override the internal `ignoreResults` setting", async () => {
- const mutation: DocumentNode = gql`
- mutation ($signature: String!) {
- mini: submitMiniCoverS3DirectUpload(signature: $signature) {
- __typename
- id
- cover(maxWidth: 600, maxHeight: 400)
- }
- }
- `;
-
- const mutationData = {
- mini: {
- id: 1,
- cover: "image2",
- __typename: "Mini",
- },
- };
-
- type MutationData = typeof mutationData;
-
- interface MutationVariables {
- signature: string;
- }
-
- const link = mockSingleLink({
- request: { query: mutation, variables: { signature: "1233" } },
- result: { data: mutationData },
- });
-
- const cache = new Cache({ addTypename: false });
- const client = new ApolloClient({ link, cache });
-
- let renderCount = 0;
- const MutationContainer = graphql(
- mutation,
- { options: { ignoreResults: false } }
- )(
- class extends React.Component<
- ChildProps
- > {
- render() {
- switch (renderCount) {
- case 0:
- expect(this.props.result!.loading).toBeFalsy();
- setTimeout(() => {
- this.props.mutate!().then((result) => {
- expect(result && result.data).toEqual(mutationData);
- });
- });
- break;
- case 1:
- expect(this.props.result!.loading).toBeTruthy();
- break;
- case 2:
- expect(this.props.result!.loading).toBeFalsy();
- default: // Do nothing
- }
-
- renderCount += 1;
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(renderCount).toBe(3);
- });
- });
-});
diff --git a/src/react/hoc/__tests__/mutations/recycled-queries.test.tsx b/src/react/hoc/__tests__/mutations/recycled-queries.test.tsx
deleted file mode 100644
index 67f3403006b..00000000000
--- a/src/react/hoc/__tests__/mutations/recycled-queries.test.tsx
+++ /dev/null
@@ -1,432 +0,0 @@
-import React from "react";
-import { render, waitFor } from "@testing-library/react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import {
- ApolloCache,
- ApolloClient,
- MutationUpdaterFunction,
-} from "../../../../core";
-import { ApolloProvider } from "../../../context";
-import { InMemoryCache as Cache } from "../../../../cache";
-import { MutationFunction } from "../../../types/types";
-import { mockSingleLink } from "../../../../testing";
-import { graphql } from "../../graphql";
-import { ChildProps } from "../../types";
-
-describe("graphql(mutation) update queries", () => {
- // This is a long test that keeps track of a lot of stuff. It is testing
- // whether or not the `options.update` reducers will run even when a given
- // container component is unmounted.
- //
- // It does this with the following procedure:
- //
- // 1. Mount a mutation component.
- // 2. Mount a query component.
- // 3. Run the mutation in the mutation component.
- // 4. Check the props in the query component.
- // 5. Unmount the query component.
- // 6. Run the mutation in the mutation component again.
- // 7. Remount the query component.
- // 8. Check the props in the query component to confirm that the mutation
- // that was run while we were unmounted changed the query component’s
- // props.
- //
- // There are also a lot more assertions on the way to make sure everything is
- // going as smoothly as planned.
- it("will run `update` for a previously mounted component", async () => {
- const query: DocumentNode = gql`
- query todos {
- todo_list {
- id
- title
- tasks {
- id
- text
- completed
- }
- }
- }
- `;
-
- interface QueryData {
- todo_list: {
- id: string;
- title: string;
- tasks: { id: string; text: string; completed: boolean }[];
- };
- }
-
- const mutation: DocumentNode = gql`
- mutation createTodo {
- createTodo {
- id
- text
- completed
- }
- }
- `;
-
- const mutationData = {
- createTodo: {
- id: "99",
- text: "This one was created with a mutation.",
- completed: true,
- },
- };
- type MutationData = typeof mutationData;
-
- let todoUpdateQueryCount = 0;
- const update: MutationUpdaterFunction<
- MutationData,
- Record,
- Record,
- ApolloCache
- > = (proxy, result) => {
- todoUpdateQueryCount++;
- const data = JSON.parse(
- JSON.stringify(proxy.readQuery({ query })) // read from cache
- );
- data!.todo_list.tasks.push(result.data!.createTodo); // update value
- proxy.writeQuery({ query, data }); // write to cache
- };
-
- const expectedData = {
- todo_list: { id: "123", title: "how to apollo", tasks: [] },
- };
-
- const link = mockSingleLink(
- {
- request: { query },
- result: { data: expectedData },
- },
- { request: { query: mutation }, result: { data: mutationData } },
- { request: { query: mutation }, result: { data: mutationData } }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let mutate: MutationFunction;
-
- const MyMutation = graphql<{}, MutationData>(mutation, {
- options: () => ({ update }),
- })(
- class extends React.Component> {
- componentDidMount() {
- mutate = this.props.mutate!;
- }
-
- render() {
- return null;
- }
- }
- );
-
- let queryMountCount = 0;
- let queryUnmountCount = 0;
- let queryRenderCount = 0;
- const testFailures: any[] = [];
-
- const MyQuery = graphql<{}, QueryData>(query)(
- class extends React.Component> {
- componentDidMount() {
- queryMountCount++;
- }
-
- componentWillUnmount() {
- queryUnmountCount++;
- }
-
- render() {
- try {
- switch (queryRenderCount) {
- case 0:
- expect(this.props.data!.loading).toBeTruthy();
- expect(this.props.data!.todo_list).toBeFalsy();
- break;
- case 1:
- expect(this.props.data!.loading).toBeFalsy();
- expect(this.props.data!.todo_list).toEqual({
- id: "123",
- title: "how to apollo",
- tasks: [],
- });
- break;
- case 2:
- expect(this.props.data!.loading).toBeFalsy();
- expect(queryMountCount).toBe(1);
- expect(this.props.data!.todo_list).toEqual({
- id: "123",
- title: "how to apollo",
- tasks: [
- {
- id: "99",
- text: "This one was created with a mutation.",
- completed: true,
- },
- ],
- });
- break;
- case 3:
- expect(this.props.data!.loading).toBeFalsy();
- expect(queryMountCount).toBe(1);
- expect(this.props.data!.todo_list).toEqual({
- id: "123",
- title: "how to apollo",
- tasks: [
- {
- id: "99",
- text: "This one was created with a mutation.",
- completed: true,
- },
- {
- id: "99",
- text: "This one was created with a mutation.",
- completed: true,
- },
- ],
- });
- break;
- default:
- throw new Error("too many rerenders");
- }
- } catch (e) {
- testFailures.push(e);
- }
-
- queryRenderCount += 1;
- return null;
- }
- }
- );
-
- const { unmount: mutationUnmount } = render(
-
-
-
- );
-
- const { unmount: query1Unmount } = render(
-
-
-
- );
-
- let resolveLastTimeout: () => void;
- const allTimeoutsFinished = new Promise((r) => {
- resolveLastTimeout = r;
- });
-
- const catchingSetTimeout = (cb: (args: void) => void, ms: number) => {
- return setTimeout(() => {
- try {
- cb();
- } catch (e) {
- testFailures.push(e);
- }
- }, ms);
- };
-
- catchingSetTimeout(() => {
- mutate();
-
- catchingSetTimeout(() => {
- expect(queryUnmountCount).toBe(0);
- query1Unmount();
- expect(queryUnmountCount).toBe(1);
-
- catchingSetTimeout(() => {
- mutate();
-
- catchingSetTimeout(() => {
- const { unmount: query2Unmount } = render(
-
-
-
- );
-
- catchingSetTimeout(() => {
- mutationUnmount();
- query2Unmount();
-
- expect(todoUpdateQueryCount).toBe(2);
- expect(queryMountCount).toBe(2);
- expect(queryUnmountCount).toBe(2);
- resolveLastTimeout!();
- }, 5);
- }, 5);
- }, 5);
- }, 6);
- }, 5);
-
- await waitFor(() => {
- expect(queryRenderCount).toBe(4);
- });
- await allTimeoutsFinished;
- if (testFailures.length > 0) {
- throw testFailures[0];
- }
- });
-
- it("will run `refetchQueries` for a recycled queries", async () => {
- const mutation: DocumentNode = gql`
- mutation createTodo {
- createTodo {
- id
- text
- completed
- }
- }
- `;
-
- const mutationData = {
- createTodo: {
- id: "99",
- text: "This one was created with a mutation.",
- completed: true,
- },
- };
-
- type MutationData = typeof mutationData;
-
- const query: DocumentNode = gql`
- query todos($id: ID!) {
- todo_list(id: $id) {
- id
- title
- tasks {
- id
- text
- completed
- }
- }
- }
- `;
-
- interface QueryData {
- todo_list: {
- id: string;
- title: string;
- tasks: { id: string; text: string; completed: boolean }[];
- };
- }
-
- interface QueryVariables {
- id: string;
- }
-
- const data = {
- todo_list: { id: "123", title: "how to apollo", tasks: [] },
- };
-
- const updatedData = {
- todo_list: {
- id: "123",
- title: "how to apollo",
- tasks: [mutationData.createTodo],
- },
- };
-
- const link = mockSingleLink(
- { request: { query, variables: { id: "123" } }, result: { data } },
- { request: { query: mutation }, result: { data: mutationData } },
- {
- request: { query, variables: { id: "123" } },
- result: { data: updatedData },
- }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let mutate: MutationFunction;
-
- const Mutation = graphql<{}, MutationData>(mutation)(
- class extends React.Component> {
- componentDidMount() {
- mutate = this.props.mutate!;
- }
-
- render() {
- return null;
- }
- }
- );
-
- let queryMountCount = 0;
- let queryRenderCount = 0;
-
- const Query = graphql(query)(
- class extends React.Component<
- ChildProps
- > {
- componentDidMount() {
- queryMountCount++;
- }
-
- render() {
- switch (queryRenderCount) {
- case 0:
- expect(this.props.data!.loading).toBeTruthy();
- expect(this.props.data!.todo_list).toBeFalsy();
- break;
- case 1:
- expect(this.props.data!.loading).toBeFalsy();
- expect(this.props.data!.todo_list).toEqual({
- id: "123",
- title: "how to apollo",
- tasks: [],
- });
- break;
- case 2:
- expect(this.props.data!.loading).toBeFalsy();
- expect(queryMountCount).toBe(1);
- expect(this.props.data!.todo_list).toEqual(updatedData.todo_list);
- break;
- case 3:
- expect(this.props.data!.loading).toBeFalsy();
- expect(this.props.data!.todo_list).toEqual(updatedData.todo_list);
- break;
- default:
- }
- queryRenderCount += 1;
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- render(
-
-
-
- );
-
- setTimeout(() => {
- mutate({ refetchQueries: [{ query, variables: { id: "123" } }] }).then(
- () => {
- setTimeout(() => {
- render(
-
-
-
- );
- });
- }
- );
- });
-
- await waitFor(() => {
- expect(queryRenderCount).toBe(4);
- });
- });
-});
diff --git a/src/react/hoc/__tests__/queries/__snapshots__/lifecycle.test.tsx.snap b/src/react/hoc/__tests__/queries/__snapshots__/lifecycle.test.tsx.snap
deleted file mode 100644
index 6966aa5793d..00000000000
--- a/src/react/hoc/__tests__/queries/__snapshots__/lifecycle.test.tsx.snap
+++ /dev/null
@@ -1,9 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`[queries] lifecycle handles asynchronous racecondition with prefilled data from the server 1`] = `
-
-`;
diff --git a/src/react/hoc/__tests__/queries/api.test.tsx b/src/react/hoc/__tests__/queries/api.test.tsx
deleted file mode 100644
index d8b0da578e5..00000000000
--- a/src/react/hoc/__tests__/queries/api.test.tsx
+++ /dev/null
@@ -1,337 +0,0 @@
-import React from "react";
-import { render, waitFor } from "@testing-library/react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import { ApolloClient } from "../../../../core";
-import { ApolloProvider } from "../../../context";
-import { InMemoryCache as Cache } from "../../../../cache";
-import { itAsync, mockSingleLink } from "../../../../testing";
-import { graphql } from "../../graphql";
-import { ChildProps } from "../../types";
-
-describe("[queries] api", () => {
- const consoleWarn = console.warn;
- beforeAll(() => {
- console.warn = () => null;
- });
-
- afterAll(() => {
- console.warn = consoleWarn;
- });
-
- itAsync("exposes refetch as part of the props api", (resolve, reject) => {
- const query: DocumentNode = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
- const variables = { first: 1 };
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const link = mockSingleLink(
- { request: { query, variables }, result: { data: data1 } },
- { request: { query, variables }, result: { data: data1 } },
- { request: { query, variables: { first: 2 } }, result: { data: data1 } }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let hasRefetched = false,
- count = 0;
-
- interface Props {
- first: number;
- }
- interface Data {
- allPeople: { people: [{ name: string }] };
- }
-
- let done = false;
- const Container = graphql(query)(
- class extends React.Component> {
- componentDidUpdate() {
- const { data } = this.props;
- try {
- if (count === 0) expect(data!.loading).toBeFalsy(); // first data
- if (count === 1) expect(data!.loading).toBeTruthy(); // first refetch
- if (count === 2) expect(data!.loading).toBeFalsy(); // second data
- if (count === 3) expect(data!.loading).toBeTruthy(); // second refetch
- if (count === 4) expect(data!.loading).toBeFalsy(); // third data
- count++;
- if (hasRefetched) return;
- hasRefetched = true;
- expect(data!.refetch).toBeTruthy();
- expect(data!.refetch instanceof Function).toBeTruthy();
- data!
- .refetch()
- .then((result: any) => {
- expect(result.data).toEqual(data1);
- return data!
- .refetch({ first: 2 }) // new variables
- .then((response: any) => {
- expect(response.data).toEqual(data1);
- expect(data!.allPeople).toEqual(data1.allPeople);
- done = true;
- });
- })
- .catch(reject);
- } catch (e) {
- reject(e);
- }
- }
-
- render() {
- expect(this.props.data!.refetch).toBeTruthy();
- expect(this.props.data!.refetch instanceof Function).toBeTruthy();
- return {this.props.first}
;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => expect(done).toBeTruthy()).then(resolve, reject);
- });
-
- itAsync(
- "exposes subscribeToMore as part of the props api",
- (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const link = mockSingleLink({
- request: { query },
- result: {
- data: { allPeople: { people: [{ name: "Luke Skywalker" }] } },
- },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- // example of loose typing
- const Container = graphql(query)(
- class extends React.Component {
- render() {
- const { data } = this.props;
- if (data && !data.loading) {
- expect(data!.subscribeToMore).toBeTruthy();
- expect(data!.subscribeToMore instanceof Function).toBeTruthy();
- done = true;
- }
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => expect(done).toBeTruthy()).then(resolve, reject);
- }
- );
-
- itAsync("exposes fetchMore as part of the props api", (resolve, reject) => {
- const query: DocumentNode = gql`
- query people($skip: Int, $first: Int) {
- allPeople(first: $first, skip: $skip) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const data1 = { allPeople: { people: [{ name: "Leia Skywalker" }] } };
-
- type Data = typeof data;
-
- const variables = { skip: 1, first: 1 };
- const variables2 = { skip: 2, first: 1 };
-
- type Variables = typeof variables;
-
- const link = mockSingleLink(
- { request: { query, variables }, result: { data } },
- { request: { query, variables: variables2 }, result: { data: data1 } }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let count = 0;
- const Container = graphql<{}, Data, Variables>(query, {
- options: () => ({ variables }),
- })(
- class extends React.Component> {
- componentDidUpdate() {
- const { props } = this;
- if (count === 0) {
- expect(props.data!.fetchMore).toBeTruthy();
- expect(props.data!.fetchMore instanceof Function).toBeTruthy();
- props
- .data!.fetchMore({
- variables: { skip: 2 },
- updateQuery: (prev: any, { fetchMoreResult }) => ({
- allPeople: {
- people: prev.allPeople.people.concat(
- fetchMoreResult!.allPeople.people
- ),
- },
- }),
- })
- .then((result: any) => {
- expect(result.data.allPeople.people).toEqual(
- data1.allPeople.people
- );
- })
- .catch(reject);
- } else if (count === 1) {
- expect(props.data!.variables).toEqual(variables);
- expect(props.data!.loading).toBeFalsy();
- expect(props.data!.allPeople!.people).toEqual(
- data.allPeople.people.concat(data1.allPeople.people)
- );
- // This ends the test (passing).
- setTimeout(() => resolve(), 20);
- } else {
- throw new Error("should not reach this point");
- }
- count++;
- }
-
- render() {
- expect(this.props.data!.fetchMore).toBeTruthy();
- expect(this.props.data!.fetchMore instanceof Function).toBeTruthy();
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
- });
-
- itAsync(
- "reruns props function after query results change via fetchMore",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people($cursor: Int) {
- allPeople(cursor: $cursor) {
- cursor
- people {
- name
- }
- }
- }
- `;
- const vars1 = { cursor: undefined };
- const data1 = {
- allPeople: { cursor: 1, people: [{ name: "Luke Skywalker" }] },
- };
- const vars2 = { cursor: 1 };
- const data2 = {
- allPeople: { cursor: 2, people: [{ name: "Leia Skywalker" }] },
- };
-
- type Data = typeof data1;
- type Variables = { cursor: number | undefined };
-
- const link = mockSingleLink(
- { request: { query, variables: vars1 }, result: { data: data1 } },
- { request: { query, variables: vars2 }, result: { data: data2 } }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let isUpdated = false;
-
- type FinalProps = {
- loading: boolean;
- people?: { name: string }[];
- getMorePeople?: () => void;
- };
-
- let done = false;
- const Container = graphql<{}, Data, Variables, FinalProps>(query, {
- props({ data }) {
- const { loading, allPeople, fetchMore } = data!;
-
- if (loading) return { loading };
- const { cursor, people } = allPeople!;
- return {
- loading: false,
- people,
- getMorePeople: () =>
- fetchMore({
- variables: { cursor },
- updateQuery(prev, { fetchMoreResult }) {
- const {
- allPeople: { cursor, people },
- } = fetchMoreResult!;
- return {
- allPeople: {
- cursor,
- people: [...people, ...prev.allPeople.people],
- },
- };
- },
- }),
- };
- },
- })(
- class extends React.Component {
- render() {
- if (!this.props.loading) {
- if (isUpdated) {
- expect(this.props.people!.length).toBe(2);
- done = true;
- } else {
- isUpdated = true;
- expect(this.props.people).toEqual(data1.allPeople.people);
- this.props.getMorePeople!();
- }
- }
-
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => expect(done).toBe(true)).then(resolve, reject);
- }
- );
-});
diff --git a/src/react/hoc/__tests__/queries/errors.test.tsx b/src/react/hoc/__tests__/queries/errors.test.tsx
deleted file mode 100644
index dfe92de05ce..00000000000
--- a/src/react/hoc/__tests__/queries/errors.test.tsx
+++ /dev/null
@@ -1,842 +0,0 @@
-import React from "react";
-import { render, waitFor } from "@testing-library/react";
-import gql from "graphql-tag";
-import { withState } from "./recomposeWithState";
-import { DocumentNode } from "graphql";
-
-import { ApolloClient } from "../../../../core";
-import { ApolloProvider } from "../../../context";
-import { InMemoryCache as Cache } from "../../../../cache";
-import { QueryResult } from "../../../types/types";
-import { itAsync, mockSingleLink } from "../../../../testing";
-import { Query } from "../../../components/Query";
-import { graphql } from "../../graphql";
-import { ChildProps, DataValue } from "../../types";
-
-describe("[queries] errors", () => {
- let error: typeof console.error;
- beforeEach(() => {
- error = console.error;
- console.error = jest.fn(() => {});
- });
- afterEach(() => {
- console.error = error;
- });
-
- // errors
- itAsync("does not swallow children errors", (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- class ErrorBoundary extends React.Component {
- componentDidCatch(e: Error) {
- expect(e.message).toMatch(/bar is not a function/);
- done = true;
- }
-
- render() {
- // eslint-disable-next-line testing-library/no-node-access
- return this.props.children;
- }
- }
- let bar: any;
- const ContainerWithData = graphql(query)(() => {
- bar(); // this will throw
- return null;
- });
-
- render(
-
-
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- });
-
- it("can unmount without error", () => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const ContainerWithData = graphql(query)(() => null);
-
- const { unmount } = render(
-
-
-
- ) as any;
-
- try {
- unmount();
- } catch (e: any) {
- throw new Error(e);
- }
- });
-
- itAsync("passes any GraphQL errors in props", (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const link = mockSingleLink({
- request: { query },
- error: new Error("boo"),
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const ErrorContainer = graphql(query)(
- class extends React.Component {
- componentDidUpdate() {
- const { data } = this.props;
- expect(data!.error).toBeTruthy();
- expect(data!.error!.networkError).toBeTruthy();
- done = true;
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- });
-
- describe("uncaught exceptions", () => {
- const consoleWarn = console.warn;
- beforeAll(() => {
- console.warn = () => null;
- });
-
- afterAll(() => {
- console.warn = consoleWarn;
- });
-
- let unhandled: any[] = [];
- function handle(reason: any) {
- unhandled.push(reason);
- }
- beforeEach(() => {
- unhandled = [];
- process.on("unhandledRejection", handle);
- });
- afterEach(() => {
- process.removeListener("unhandledRejection", handle);
- });
-
- it("does not log when you change variables resulting in an error", async () => {
- const query: DocumentNode = gql`
- query people($var: Int) {
- allPeople(first: $var) {
- people {
- name
- }
- }
- }
- `;
- const var1 = { var: 1 };
- const data = { allPeople: { people: { name: "Luke Skywalker" } } };
- const var2 = { var: 2 };
- const link = mockSingleLink(
- {
- request: { query, variables: var1 },
- result: { data },
- },
- {
- request: { query, variables: var2 },
- error: new Error("boo"),
- }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- type Data = typeof data;
- type Vars = typeof var1;
-
- interface Props extends Vars {
- var: number;
- setVar: (val: number) => number;
- }
-
- let iteration = 0;
- let done = false;
- const ErrorContainer = withState(
- "var",
- "setVar",
- 1
- )(
- // @ts-expect-error
- graphql(query)(
- class extends React.Component> {
- componentDidUpdate() {
- const { props } = this;
- iteration += 1;
-
- if (iteration === 1) {
- // initial loading state is done, we have data
- expect(props.data!.loading).toBe(false);
- expect(props.data!.allPeople).toEqual(data.allPeople);
- props.setVar(2);
- } else if (iteration === 2) {
- expect(props.data!.loading).toBe(true);
- expect(props.data!.allPeople).toBeUndefined();
- } else if (iteration === 3) {
- expect(props.data!.loading).toBe(false);
- expect(props.data!.allPeople).toBeUndefined();
- // the second request had an error!
- expect(props.data!.error).toBeTruthy();
- expect(props.data!.error!.networkError).toBeTruthy();
- // // We need to set a timeout to ensure the unhandled rejection is swept up
- setTimeout(() => {
- expect(unhandled.length).toEqual(0);
- done = true;
- });
- }
- }
- render() {
- return null;
- }
- }
- )
- );
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(iteration).toBe(3);
- });
- await waitFor(() => {
- expect(done).toBeTruthy();
- });
- });
- });
-
- it("will not log a warning when there is an error that is not caught in the render method when using query", () =>
- new Promise((resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
-
- interface Data {
- allPeople: {
- people: { name: string }[];
- };
- }
-
- const link = mockSingleLink({
- request: { query },
- error: new Error("oops"),
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const origError = console.error;
- const errorMock = jest.fn();
- console.error = errorMock;
-
- let renderCount = 0;
- @graphql<{}, Data>(query)
- class UnhandledErrorComponent extends React.Component<
- ChildProps<{}, Data>
- > {
- render(): React.ReactNode {
- try {
- switch (renderCount++) {
- case 0:
- expect(this.props.data!.loading).toEqual(true);
- break;
- case 1:
- // Noop. Don’t handle the error so a warning will be logged to the console.
- expect(renderCount).toBe(2);
- expect(errorMock.mock.calls.length).toBe(0);
- break;
- default:
- throw new Error("Too many renders.");
- }
- } catch (error) {
- reject(error);
- } finally {
- console.error = origError;
- }
- return null;
- }
- }
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(renderCount).toBe(2);
- }).then(resolve, reject);
- }));
-
- itAsync(
- "passes any cached data when there is a GraphQL error",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data;
- const link = mockSingleLink(
- { request: { query }, result: { data } },
- { request: { query }, error: new Error("No Network Connection") }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let count = 0;
- const Container = graphql<{}, Data>(query, {
- options: { notifyOnNetworkStatusChange: true },
- })(
- class extends React.Component> {
- componentDidUpdate() {
- const { props } = this;
- try {
- switch (count++) {
- case 0:
- expect(props.data!.allPeople).toEqual(data.allPeople);
- setTimeout(() => {
- props.data!.refetch().catch(() => null);
- });
- break;
- case 1:
- expect(props.data!.loading).toBeTruthy();
- expect(props.data!.allPeople).toEqual(data.allPeople);
- break;
- case 2:
- expect(props.data!.loading).toBeFalsy();
- expect(props.data!.error).toBeTruthy();
- expect(props.data!.allPeople).toEqual(data.allPeople);
- break;
- default:
- throw new Error("Unexpected fall through");
- }
- } catch (e) {
- reject(e);
- }
- }
-
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(count).toBe(3);
- }).then(resolve, reject);
- }
- );
-
- itAsync("can refetch after there was a network error", (resolve, reject) => {
- const query: DocumentNode = gql`
- query somethingelse {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const dataTwo = { allPeople: { people: [{ name: "Princess Leia" }] } };
-
- type Data = typeof data;
- const link = mockSingleLink(
- { request: { query }, result: { data } },
- { request: { query }, error: new Error("This is an error!") },
- { request: { query }, result: { data: dataTwo } }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let count = 0;
- const noop = () => null;
- const Container = graphql<{}, Data>(query, {
- options: { notifyOnNetworkStatusChange: true },
- })(
- class extends React.Component> {
- componentDidUpdate() {
- const { props } = this;
- try {
- switch (count++) {
- case 0:
- props
- .data!.refetch()
- .then(() => {
- reject("Expected error value on first refetch.");
- })
- .catch(noop);
- break;
- case 1:
- expect(props.data!.loading).toBeTruthy();
- break;
- case 2:
- expect(props.data!.loading).toBeFalsy();
- expect(props.data!.error).toBeTruthy();
- props
- .data!.refetch()
- .then(noop)
- .catch(() => {
- reject("Expected good data on second refetch.");
- });
- break;
- case 3:
- expect(props.data!.loading).toBeTruthy();
- break;
- case 4:
- expect(props.data!.loading).toBeFalsy();
- expect(props.data!.error).toBeFalsy();
- expect(props.data!.allPeople).toEqual(dataTwo.allPeople);
- break;
- default:
- throw new Error("Unexpected fall through");
- }
- } catch (e) {
- reject(e);
- }
- }
-
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(count).toBe(5);
- }).then(resolve, reject);
- });
-
- itAsync(
- "does not throw/console.err an error after a component that received a network error is unmounted",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query somethingelse {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
-
- type Data = typeof data;
- const link = mockSingleLink(
- { request: { query }, result: { data } },
- { request: { query }, error: new Error("This is an error!") }
- );
-
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
- let count = 0;
- const noop = () => null;
-
- interface ContainerOwnProps {
- hideContainer: Function;
- }
-
- interface QueryChildProps {
- data: DataValue;
- hideContainer: Function;
- }
-
- let done = false;
- const Container = graphql(
- query,
- {
- options: { notifyOnNetworkStatusChange: true },
- props: (something) => {
- return {
- data: something.data!,
- hideContainer: something!.ownProps.hideContainer,
- };
- },
- }
- )(
- class extends React.Component> {
- componentDidUpdate() {
- const { props } = this;
- try {
- switch (count++) {
- case 0:
- props
- .data!.refetch()
- .then(() => {
- reject("Expected error value on first refetch.");
- })
- .catch(noop);
- break;
- case 2:
- expect(props.data!.loading).toBeFalsy();
- expect(props.data!.error).toBeTruthy();
- const origError = console.error;
- const errorMock = jest.fn();
- console.error = errorMock;
- props.hideContainer();
- setTimeout(() => {
- expect(errorMock.mock.calls.length).toEqual(0);
- console.error = origError;
- done = true;
- }, 100);
- break;
- default:
- if (count < 2) {
- throw new Error("Unexpected fall through");
- }
- }
- } catch (err) {
- reject(err);
- }
- }
- render() {
- return null;
- }
- }
- );
-
- class Switcher extends React.Component {
- constructor(props: any) {
- super(props);
- this.state = {
- showContainer: true,
- };
- }
- render() {
- const {
- state: { showContainer },
- } = this;
- if (showContainer) {
- return (
- this.setState({ showContainer: false })}
- />
- );
- }
- return null;
- }
- }
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBeTruthy();
- }).then(resolve, reject);
- }
- );
-
- itAsync(
- "correctly sets loading state on remount after a network error",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query somethingelse {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const dataTwo = { allPeople: { people: [{ name: "Princess Leia" }] } };
-
- type Data = typeof data;
- const link = mockSingleLink(
- { request: { query }, error: new Error("This is an error!") },
- { request: { query }, result: { data: dataTwo } }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let count = 0;
- type ContainerOwnProps = { toggle: () => void };
- const Container = graphql(query, {
- options: { notifyOnNetworkStatusChange: true },
- })(
- class extends React.Component> {
- render() {
- switch (count) {
- case 0:
- expect(this.props.data!.loading).toBe(true);
- break;
- case 1:
- expect(this.props.data!.loading).toBe(false);
- expect(this.props.data!.error!.networkError!.message).toMatch(
- /This is an error/
- );
- // unmount this component
- setTimeout(() => {
- this.props.toggle();
- }, 0);
- setTimeout(() => {
- // remount after 50 ms
- this.props.toggle();
- }, 50);
- break;
- case 2:
- expect(this.props.data!.loading).toBe(true);
- break;
- case 3:
- expect(this.props.data!.loading).toBe(false);
- expect(this.props.data!.allPeople).toEqual(dataTwo.allPeople);
- break;
- default:
- throw new Error("Too many renders.");
- }
- count += 1;
-
- return null;
- }
- }
- );
-
- type Toggle = () => void;
- type OwnProps = { children: (toggle: Toggle) => any };
- class Manager extends React.Component {
- constructor(props: any) {
- super(props);
- this.state = { show: true };
- }
- render() {
- if (!this.state.show) return null;
- // eslint-disable-next-line testing-library/no-node-access
- return this.props.children(() =>
- this.setState(({ show }) => ({ show: !show }))
- );
- }
- }
-
- render(
-
- {(toggle: Toggle) => }
-
- );
-
- waitFor(() => expect(count).toBe(4)).then(resolve, reject);
- }
- );
-
- describe("errorPolicy", () => {
- itAsync(
- "passes any GraphQL errors in props along with data",
- (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const link = mockSingleLink({
- request: { query },
- result: {
- data: {
- allPeople: {
- people: null,
- },
- },
- errors: [new Error("this is an error")],
- },
- });
-
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const ErrorContainer = graphql(query, {
- options: { errorPolicy: "all" },
- })(
- class extends React.Component {
- componentDidUpdate() {
- const { data } = this.props;
- expect(data!.error).toBeTruthy();
- expect(data!.error!.graphQLErrors[0].message).toEqual(
- "this is an error"
- );
- expect(data).toMatchObject({ allPeople: { people: null } });
- done = true;
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- }
- );
-
- itAsync(
- "passes any GraphQL errors in props along with data [component]",
- (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const link = mockSingleLink({
- request: { query },
- result: {
- data: {
- allPeople: {
- people: null,
- },
- },
- errors: [new Error("this is an error")],
- },
- });
-
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- class ErrorContainer extends React.Component {
- componentDidUpdate() {
- const { props } = this;
- expect(props.error).toBeTruthy();
- expect(props.error!.graphQLErrors[0].message).toEqual(
- "this is an error"
- );
- expect(props.data!.allPeople!).toMatchObject({ people: null });
- done = true;
- }
- render() {
- return null;
- }
- }
-
- render(
-
-
- {(props: any) => }
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- }
- );
- });
-});
diff --git a/src/react/hoc/__tests__/queries/index.test.tsx b/src/react/hoc/__tests__/queries/index.test.tsx
deleted file mode 100644
index 45eaba87b96..00000000000
--- a/src/react/hoc/__tests__/queries/index.test.tsx
+++ /dev/null
@@ -1,931 +0,0 @@
-import React from "react";
-import PropTypes from "prop-types";
-import { render, waitFor } from "@testing-library/react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import { ApolloClient } from "../../../../core";
-import { ApolloProvider } from "../../../context";
-import { InMemoryCache as Cache } from "../../../../cache";
-import { ApolloLink } from "../../../../link/core";
-import { itAsync, mockSingleLink } from "../../../../testing";
-import { graphql } from "../../graphql";
-import { ChildProps, DataProps } from "../../types";
-
-describe("queries", () => {
- let error: typeof console.error;
- beforeEach(() => {
- error = console.error;
- console.error = jest.fn(() => {});
- });
-
- afterEach(() => {
- console.error = error;
- });
-
- // general api
- it("binds a query to props", async () => {
- let done = false;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const link = mockSingleLink({
- request: { query },
- result: { data: { allPeople: { people: [{ name: "Luke Skywalker" }] } } },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Data {
- allPeople?: {
- people: { name: string }[];
- };
- }
-
- const ContainerWithData = graphql(query)(({
- data,
- }: DataProps) => {
- expect(data).toBeTruthy();
- done = true;
- return null;
- });
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(done).toBe(true);
- });
- });
-
- itAsync("includes the variables in the props", (resolve, reject) => {
- const TIME_SCALE = 5000;
- let renderCount = 0;
- const query: DocumentNode = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
-
- const variables = { first: 1 };
- const link = mockSingleLink({
- request: { query, variables },
- result: { data: { allPeople: { people: [{ name: "Luke Skywalker" }] } } },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- // Ensure variable types work correctly here
-
- interface Data {
- allPeople: {
- people: {
- name: string;
- };
- };
- }
-
- interface Variables {
- first: number;
- }
-
- const ContainerWithData = graphql(query)(({
- data,
- }: ChildProps) => {
- expect(data).toBeTruthy();
- expect(data!.variables).toEqual(variables);
- renderCount += 1;
- return null;
- });
-
- render(
-
-
-
- );
-
- waitFor(
- () => {
- expect(renderCount).toBe(2);
- },
- { timeout: TIME_SCALE }
- ).then(resolve, reject);
- });
-
- itAsync(
- "should update query variables when props change",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people($someId: ID) {
- allPeople(someId: $someId) {
- people {
- name
- }
- }
- }
- `;
-
- const link = mockSingleLink(
- {
- request: { query, variables: { someId: 1 } },
- result: {
- data: { allPeople: { people: [{ name: "Luke Skywalker" }] } },
- },
- },
- {
- request: { query, variables: { someId: 2 } },
- result: {
- data: { allPeople: { people: [{ name: "Darth Vader" }] } },
- },
- }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Data {
- allPeople: {
- people: {
- name: string;
- };
- };
- }
-
- interface Variables {
- someId: number;
- }
-
- const options = {
- options: {},
- };
-
- let count = 0;
- const ContainerWithData = graphql(
- query,
- options
- )(({ data }: ChildProps) => {
- expect(data).toBeTruthy();
- switch (++count) {
- case 1:
- expect(data!.loading).toBe(true);
- expect(data!.variables).toEqual({ someId: 1 });
- break;
- case 2:
- expect(data!.loading).toBe(true);
- expect(data!.variables).toEqual({ someId: 2 });
- break;
- case 3:
- expect(data!.loading).toBe(false);
- expect(data!.variables).toEqual({ someId: 2 });
- break;
- default:
- reject(`too many renders (${count})`);
- }
-
- return null;
- });
-
- const { rerender } = render(
-
-
-
- );
- rerender(
-
-
-
- );
-
- waitFor(() => {
- expect(count).toBe(3);
- }).then(resolve, reject);
- }
- );
-
- it("shouldn't warn about fragments", () => {
- const oldWarn = console.warn;
- const warnings: any[] = [];
- console.warn = (str: any) => warnings.push(str);
-
- try {
- graphql(gql`
- query foo {
- bar
- }
- `);
- expect(warnings.length).toEqual(0);
- } finally {
- console.warn = oldWarn;
- }
- });
-
- itAsync("executes a query", (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data;
-
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql<{}, Data>(query)(
- class extends React.Component> {
- componentDidUpdate() {
- const { props } = this;
- expect(props.data!.loading).toBeFalsy();
- expect(props.data!.allPeople).toEqual(data.allPeople);
- done = true;
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- });
-
- itAsync("executes a query with two root fields", (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- otherPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = {
- allPeople: { people: [{ name: "Luke Skywalker" }] },
- otherPeople: { people: [{ name: "Luke Skywalker" }] },
- };
- type Data = typeof data;
-
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql<{}, Data>(query)(
- class extends React.Component> {
- componentDidUpdate() {
- const { props } = this;
- expect(props.data!.loading).toBeFalsy();
- expect(props.data!.allPeople).toEqual(data.allPeople);
- expect(props.data!.otherPeople).toEqual(data.otherPeople);
- done = true;
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- });
-
- itAsync("maps props as variables if they match", (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data;
-
- const variables = { first: 1 };
- type Vars = typeof variables;
-
- const link = mockSingleLink({
- request: { query, variables },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql(query)(
- class extends React.Component> {
- componentDidUpdate() {
- const { props } = this;
- expect(props.data!.loading).toBeFalsy();
- expect(props.data!.allPeople).toEqual(data.allPeople);
- expect(props.data!.variables).toEqual(this.props.data!.variables);
- done = true;
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- });
-
- itAsync(
- "doesn't care about the order of variables in a request",
- (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people($first: Int, $jedi: Boolean) {
- allPeople(first: $first, jedi: $jedi) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data;
- const variables = { jedi: true, first: 1 };
- type Vars = typeof variables;
-
- const mocks = [
- {
- request: {
- query,
- variables,
- },
- result: {
- data,
- },
- },
- ];
- const link = mockSingleLink.apply(null, mocks);
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
- const options = {
- options: {
- variables: {
- jedi: true,
- first: 1,
- },
- },
- };
-
- const Container = graphql<{}, Data, Vars>(
- query,
- options
- )(
- class extends React.Component> {
- componentDidUpdate() {
- const { props } = this;
- try {
- expect(props.data!.loading).toBeFalsy();
- expect(props.data!.allPeople).toEqual(data.allPeople);
- done = true;
- } catch (error) {
- reject(error);
- }
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- }
- );
-
- itAsync(
- "allows falsy values in the mapped variables from props",
- (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data;
-
- const variables = { first: null };
- type Vars = typeof variables;
-
- const link = mockSingleLink({
- request: { query, variables },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql, Data, Vars>(query)(
- class extends React.Component, Data, Vars>> {
- componentDidUpdate() {
- const { props } = this;
- expect(props.data!.loading).toBeFalsy();
- expect(props.data!.allPeople).toEqual(data.allPeople);
- done = true;
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- }
- );
-
- it("doesn't error on optional required props", () => {
- const query: DocumentNode = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data;
-
- const variables = { first: 1 };
- type Vars = typeof variables;
-
- const link = mockSingleLink({
- request: { query, variables },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
- const Container = graphql(query)(() => null);
-
- let errorCaught = null;
- try {
- const { unmount } = render(
-
-
-
- );
- unmount();
- } catch (e) {
- errorCaught = e;
- }
-
- expect(errorCaught).toBeNull();
- });
-
- // context
- itAsync("allows context through updates", (resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data;
-
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql<{}, Data>(query)(
- class extends React.Component> {
- componentDidUpdate() {
- const { props } = this;
- expect(props.data!.loading).toBeFalsy();
- expect(props.data!.allPeople).toEqual(data.allPeople);
- }
- render() {
- // eslint-disable-next-line testing-library/no-node-access
- return {this.props.children}
;
- }
- }
- );
-
- class ContextContainer extends React.Component<
- React.PropsWithChildren,
- { color: string }
- > {
- constructor(props: {}) {
- super(props);
- this.state = { color: "purple" };
- }
-
- getChildContext() {
- return { color: this.state.color };
- }
-
- componentDidMount() {
- setTimeout(() => {
- this.setState({ color: "green" });
- }, 50);
- }
-
- render() {
- // eslint-disable-next-line testing-library/no-node-access
- return {this.props.children}
;
- }
- }
-
- (ContextContainer as any).childContextTypes = {
- color: PropTypes.string,
- };
-
- let count = 0;
- let done = false;
- class ChildContextContainer extends React.Component {
- render() {
- const { color } = this.context as any;
- if (count === 0) expect(color).toBe("purple");
- if (count === 1) {
- expect(color).toBe("green");
- done = true;
- }
-
- count++;
- // eslint-disable-next-line testing-library/no-node-access
- return {this.props.children}
;
- }
- }
-
- (ChildContextContainer as any).contextTypes = {
- color: PropTypes.string,
- };
-
- render(
-
-
-
-
-
-
-
- );
-
- waitFor(() => expect(done).toBeTruthy()).then(resolve, reject);
- });
-
- // meta
- it("stores the component name", () => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data;
- const Container = graphql<{}, Data>(query)(
- class Container extends React.Component> {
- render() {
- return null;
- }
- }
- );
-
- expect(Container.displayName).toEqual("Apollo(Container)");
- });
-
- it("uses a custom wrapped component name when 'alias' is specified", () => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- @graphql(query, {
- alias: "withFoo",
- })
- class Container extends React.Component {
- render(): React.ReactNode {
- return null;
- }
- }
- // );
-
- // Not sure why I have to cast Container to any
- expect((Container as any).displayName).toEqual("withFoo(Container)");
- });
-
- itAsync("passes context to the link", (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const link = new ApolloLink((o, f) => {
- expect(o.getContext().fromProps).toBe(true);
- done = true;
- return f ? f(o) : null;
- }).concat(
- mockSingleLink({
- request: { query },
- result: {
- data: { allPeople: { people: [{ name: "Luke Skywalker" }] } },
- },
- })
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Data {
- allPeople?: {
- people: { name: string }[];
- };
- }
-
- const ContainerWithData = graphql(query, {
- options: (props) => ({ context: { fromProps: props.context } }),
- })(() => null);
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- });
-
- describe("Return partial data", () => {
- const consoleWarn = console.warn;
- beforeAll(() => {
- console.warn = () => null;
- });
-
- afterAll(() => {
- console.warn = consoleWarn;
- });
-
- it("should not return partial cache data when `returnPartialData` is false", () => {
- const cache = new Cache();
- const client = new ApolloClient({
- cache,
- link: ApolloLink.empty(),
- });
-
- const fullQuery = gql`
- query {
- cars {
- make
- model
- repairs {
- date
- description
- }
- }
- }
- `;
-
- cache.writeQuery({
- query: fullQuery,
- data: {
- cars: [
- {
- __typename: "Car",
- make: "Ford",
- model: "Mustang",
- vin: "PONY123",
- repairs: [
- {
- __typename: "Repair",
- date: "2019-05-08",
- description: "Could not get after it.",
- },
- ],
- },
- ],
- },
- });
-
- const partialQuery = gql`
- query {
- cars {
- repairs {
- date
- cost
- }
- }
- }
- `;
-
- const ComponentWithData = graphql(partialQuery)(
- class Compnent extends React.Component {
- render() {
- expect(this.props.data.cars).toBeUndefined();
- return null;
- }
- }
- );
-
- const App = () => (
-
-
-
- );
-
- render( );
- });
-
- it("should return partial cache data when `returnPartialData` is true", () => {
- const cache = new Cache();
- const client = new ApolloClient({
- cache,
- link: ApolloLink.empty(),
- });
-
- const fullQuery = gql`
- query {
- cars {
- make
- model
- repairs {
- date
- description
- }
- }
- }
- `;
-
- cache.writeQuery({
- query: fullQuery,
- data: {
- cars: [
- {
- __typename: "Car",
- make: "Ford",
- model: "Mustang",
- vin: "PONY123",
- repairs: [
- {
- __typename: "Repair",
- date: "2019-05-08",
- description: "Could not get after it.",
- },
- ],
- },
- ],
- },
- });
-
- const partialQuery = gql`
- query {
- cars {
- repairs {
- date
- cost
- }
- }
- }
- `;
-
- const ComponentWithData = graphql(partialQuery, {
- options: {
- returnPartialData: true,
- },
- })(
- class Compnent extends React.Component {
- render() {
- if (!this.props.data.loading) {
- expect(this.props.data.cars).toEqual([
- {
- __typename: "Car",
- repairs: [
- {
- __typename: "Repair",
- date: "2019-05-08",
- },
- ],
- },
- ]);
- }
- return null;
- }
- }
- );
-
- const App = () => (
-
-
-
- );
-
- render( );
- });
- });
-});
diff --git a/src/react/hoc/__tests__/queries/lifecycle.test.tsx b/src/react/hoc/__tests__/queries/lifecycle.test.tsx
deleted file mode 100644
index 76134ba081c..00000000000
--- a/src/react/hoc/__tests__/queries/lifecycle.test.tsx
+++ /dev/null
@@ -1,881 +0,0 @@
-import React from "react";
-import { render, waitFor } from "@testing-library/react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import { ApolloClient } from "../../../../core";
-import { ApolloProvider } from "../../../context";
-import { InMemoryCache as Cache } from "../../../../cache";
-import { mockSingleLink } from "../../../../testing";
-import { Query as QueryComponent } from "../../../components";
-import { graphql } from "../../graphql";
-import { ChildProps, DataValue } from "../../types";
-import {
- disableActEnvironment,
- createRenderStream,
-} from "@testing-library/react-render-stream";
-
-describe("[queries] lifecycle", () => {
- // lifecycle
- it("reruns the query if it changes", async () => {
- let count = 0;
- const query: DocumentNode = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
-
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data1;
- const variables1 = { first: 1 };
- type Vars = typeof variables1;
-
- const data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } };
- const variables2 = { first: 2 };
-
- const link = mockSingleLink(
- { request: { query, variables: variables1 }, result: { data: data1 } },
- { request: { query, variables: variables2 }, result: { data: data2 } }
- );
-
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql(query, {
- options: (props) => ({
- variables: props,
- fetchPolicy: count === 0 ? "cache-and-network" : "cache-first",
- }),
- })(
- class extends React.Component> {
- render() {
- replaceSnapshot(this.props.data!);
- return null;
- }
- }
- );
-
- using _disabledAct = disableActEnvironment();
- const { takeRender, replaceSnapshot, render } =
- createRenderStream>();
- const { rerender } = await render( , {
- wrapper: ({ children }) => (
- {children}
- ),
- });
-
- {
- const { snapshot } = await takeRender();
- expect(snapshot!.loading).toBe(true);
- expect(snapshot!.allPeople).toBe(undefined);
- }
-
- {
- const { snapshot } = await takeRender();
- expect(snapshot!.loading).toBe(false);
- expect(snapshot!.variables).toEqual(variables1);
- expect(snapshot!.allPeople).toEqual(data1.allPeople);
- }
-
- await rerender( );
-
- {
- const { snapshot } = await takeRender();
- expect(snapshot!.loading).toBe(true);
- expect(snapshot!.variables).toEqual(variables2);
- expect(snapshot!.allPeople).toBe(undefined);
- }
-
- {
- const { snapshot } = await takeRender();
- expect(snapshot!.loading).toBe(false);
- expect(snapshot!.variables).toEqual(variables2);
- expect(snapshot!.allPeople).toEqual(data2.allPeople);
- }
- });
-
- it("rebuilds the queries on prop change when using `options`", async () => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data;
-
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let firstRun = true;
- let isDone = false;
- function options(props: Props) {
- if (!firstRun) {
- expect(props.listId).toBe(2);
- isDone = true;
- }
- return {};
- }
- interface Props {
- listId: number;
- }
-
- const Container = graphql(query, { options })(() => null);
-
- class ChangingProps extends React.Component<{}, { listId: number }> {
- state = { listId: 1 };
-
- componentDidMount() {
- setTimeout(() => {
- firstRun = false;
- this.setState({ listId: 2 });
- });
- }
-
- render() {
- return ;
- }
- }
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(firstRun).toBeFalsy();
- });
- await waitFor(() => {
- expect(isDone).toBeTruthy();
- });
- });
-
- it("reruns the query if just the variables change", async () => {
- let count = 0;
- const query: DocumentNode = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
-
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data1;
-
- const variables1 = { first: 1 };
- type Vars = typeof variables1;
-
- const data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } };
- const variables2 = { first: 2 };
-
- const link = mockSingleLink(
- { request: { query, variables: variables1 }, result: { data: data1 } },
- { request: { query, variables: variables2 }, result: { data: data2 } }
- );
-
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql(query, {
- options: (props) => ({ variables: props }),
- })(
- class extends React.Component> {
- componentDidUpdate(prevProps: ChildProps) {
- try {
- const { data } = this.props;
- switch (++count) {
- case 1:
- expect(prevProps.data!.loading).toBe(true);
- expect(prevProps.data!.variables).toEqual({ first: 1 });
- expect(prevProps.data!.allPeople).toBe(undefined);
- expect(data!.loading).toBe(false);
- expect(data!.variables).toEqual({ first: 1 });
- expect(data!.allPeople).toEqual(data1.allPeople);
- break;
- case 2:
- expect(data!.loading).toBe(true);
- expect(data!.variables).toEqual({ first: 2 });
- expect(data!.allPeople).toBe(undefined);
- break;
- case 3:
- expect(data!.loading).toBe(false);
- expect(data!.variables).toEqual({ first: 2 });
- expect(data!.allPeople).toEqual(data2.allPeople);
- break;
- default:
- fail(`Too many renders (${count})`);
- }
- } catch (err) {
- fail(err);
- }
- }
-
- render() {
- return null;
- }
- }
- );
-
- class ChangingProps extends React.Component {
- state = { first: 1 };
-
- componentDidMount() {
- setTimeout(() => {
- this.setState({ first: 2 });
- }, 50);
- }
-
- render() {
- return ;
- }
- }
-
- render(
-
-
-
- );
-
- await waitFor(() => expect(count).toBe(3));
- });
-
- it("reruns the queries on prop change when using passed props", async () => {
- let count = 0;
- const query: DocumentNode = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
-
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data1;
-
- const variables1 = { first: 1 };
- type Vars = typeof variables1;
-
- const data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } };
- const variables2 = { first: 2 };
-
- const link = mockSingleLink(
- { request: { query, variables: variables1 }, result: { data: data1 } },
- { request: { query, variables: variables2 }, result: { data: data2 } }
- );
-
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql(query)(
- class extends React.Component> {
- componentDidUpdate(prevProps: ChildProps) {
- try {
- const { data } = this.props;
- switch (++count) {
- case 1:
- expect(prevProps.data!.loading).toBe(true);
- expect(prevProps.data!.variables).toEqual({ first: 1 });
- expect(prevProps.data!.allPeople).toBe(undefined);
- expect(data!.loading).toBe(false);
- expect(data!.variables).toEqual({ first: 1 });
- expect(data!.allPeople).toEqual(data1.allPeople);
- break;
- case 2:
- expect(data!.loading).toBe(true);
- expect(data!.variables).toEqual({ first: 2 });
- expect(data!.allPeople).toBe(undefined);
- break;
- case 3:
- expect(data!.loading).toBe(false);
- expect(data!.variables).toEqual({ first: 2 });
- expect(data!.allPeople).toEqual(data2.allPeople);
- break;
- }
- } catch (err) {
- fail(err);
- }
- }
- render() {
- return null;
- }
- }
- );
-
- class ChangingProps extends React.Component {
- state = { first: 1 };
-
- componentDidMount() {
- setTimeout(() => {
- this.setState({ first: 2 });
- }, 50);
- }
-
- render() {
- return ;
- }
- }
-
- render(
-
-
-
- );
-
- await waitFor(() => expect(count).toBe(3));
- });
-
- it("stays subscribed to updates after irrelevant prop changes", async () => {
- const query: DocumentNode = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
- const variables = { first: 1 };
- type Vars = typeof variables;
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data1;
-
- const data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } };
- const link = mockSingleLink(
- { request: { query, variables }, result: { data: data1 } },
- { request: { query, variables }, result: { data: data2 } }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Props {
- foo: number;
- changeState: () => void;
- }
-
- let count = 0;
- const Container = graphql(query, {
- options: { variables, notifyOnNetworkStatusChange: false },
- })(
- class extends React.Component> {
- componentDidUpdate() {
- const { props } = this;
- count += 1;
- try {
- if (count === 1) {
- expect(props.foo).toEqual(42);
- expect(props.data!.loading).toEqual(false);
- expect(props.data!.allPeople).toEqual(data1.allPeople);
- props.changeState();
- } else if (count === 2) {
- expect(props.foo).toEqual(43);
- expect(props.data!.loading).toEqual(false);
- expect(props.data!.allPeople).toEqual(data1.allPeople);
- void props.data!.refetch();
- } else if (count === 3) {
- expect(props.foo).toEqual(43);
- expect(props.data!.loading).toEqual(false);
- expect(props.data!.allPeople).toEqual(data2.allPeople);
- }
- } catch (e) {
- fail(e);
- }
- }
- render() {
- return null;
- }
- }
- );
-
- class Parent extends React.Component {
- state = { foo: 42 };
- render() {
- return (
- this.setState({ foo: 43 })}
- />
- );
- }
- }
-
- render(
-
-
-
- );
-
- await waitFor(() => expect(count).toBe(3));
- });
-
- it("correctly rebuilds props on remount", async () => {
- const query: DocumentNode = gql`
- query pollingPeople {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Darth Skywalker" }] } };
- type Data = typeof data;
- const link = mockSingleLink({
- request: { query },
- result: { data },
- newData: () => ({
- data: {
- allPeople: {
- people: [{ name: `Darth Skywalker - ${Math.random()}` }],
- },
- },
- }),
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
- let app: React.ReactElement,
- count = 0;
-
- let done = false;
- let rerender: any;
- const Container = graphql<{}, Data>(query, {
- options: { pollInterval: 10, notifyOnNetworkStatusChange: false },
- })(
- class extends React.Component> {
- componentDidUpdate() {
- if (count === 1) {
- // has data
- rerender(app);
- }
-
- if (count === 2) {
- done = true;
- }
- count++;
- }
- render() {
- return null;
- }
- }
- );
-
- app = (
-
-
-
- );
-
- rerender = render(app).rerender;
-
- await waitFor(() => {
- expect(done).toBeTruthy();
- });
- });
-
- it("will re-execute a query when the client changes", async () => {
- const query: DocumentNode = gql`
- {
- a
- b
- c
- }
- `;
- const link1 = mockSingleLink(
- {
- request: { query },
- result: { data: { a: 1, b: 2, c: 3 } },
- },
- {
- request: { query },
- result: { data: { a: 1, b: 2, c: 3 } },
- }
- );
- const link2 = mockSingleLink(
- {
- request: { query },
- result: { data: { a: 4, b: 5, c: 6 } },
- },
- {
- request: { query },
- result: { data: { a: 4, b: 5, c: 6 } },
- }
- );
- const link3 = mockSingleLink({
- request: { query },
- result: { data: { a: 7, b: 8, c: 9 } },
- });
- const client1 = new ApolloClient({
- link: link1,
- cache: new Cache({ addTypename: false }),
- });
- const client2 = new ApolloClient({
- link: link2,
- cache: new Cache({ addTypename: false }),
- });
- const client3 = new ApolloClient({
- link: link3,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Data {
- a: number;
- b: number;
- c: number;
- }
- let switchClient: (client: ApolloClient) => void;
- let refetchQuery: () => void;
- let count = 0;
- let testFailures: any[] = [];
-
- const Query = graphql<{}, Data>(query, {
- options: { notifyOnNetworkStatusChange: true },
- })(
- class extends React.Component> {
- componentDidMount() {
- refetchQuery = () => this.props.data!.refetch();
- }
-
- render() {
- try {
- const { loading, a, b, c } = this.props.data!;
- switch (++count) {
- case 1:
- expect({ loading, a, b, c }).toEqual({
- loading: true,
- a: void 0,
- b: void 0,
- c: void 0,
- });
- break;
- case 2:
- expect({ loading, a, b, c }).toEqual({
- loading: false,
- a: 1,
- b: 2,
- c: 3,
- });
- refetchQuery!();
- break;
- case 3:
- expect({ loading }).toEqual({ loading: true });
- expect({ a, b, c }).toEqual({
- a: 1,
- b: 2,
- c: 3,
- });
- break;
- case 4:
- expect({ loading, a, b, c }).toEqual({
- loading: false,
- a: 1,
- b: 2,
- c: 3,
- });
- setTimeout(() => {
- switchClient!(client2);
- });
- break;
- case 5:
- expect({ loading, a, b, c }).toEqual({
- loading: true,
- a: void 0,
- b: void 0,
- c: void 0,
- });
- break;
- case 6:
- expect({ loading, a, b, c }).toEqual({
- loading: false,
- a: 4,
- b: 5,
- c: 6,
- });
- refetchQuery!();
- break;
- case 7:
- expect({ loading, a, b, c }).toEqual({
- loading: true,
- a: 4,
- b: 5,
- c: 6,
- });
- break;
- case 8:
- expect({ loading, a, b, c }).toEqual({
- loading: false,
- a: 4,
- b: 5,
- c: 6,
- });
- setTimeout(() => {
- switchClient!(client3);
- });
- break;
- case 9:
- expect({ loading, a, b, c }).toEqual({
- loading: true,
- a: void 0,
- b: void 0,
- c: void 0,
- });
- break;
- case 10:
- expect({ loading, a, b, c }).toEqual({
- loading: false,
- a: 7,
- b: 8,
- c: 9,
- });
- setTimeout(() => {
- switchClient!(client1);
- });
- break;
- case 11:
- expect({ loading, a, b, c }).toEqual({
- loading: false,
- a: 1,
- b: 2,
- c: 3,
- });
- setTimeout(() => {
- switchClient!(client3);
- });
- break;
- case 12:
- expect({ loading, a, b, c }).toEqual({
- loading: false,
- a: 7,
- b: 8,
- c: 9,
- });
- break;
- default:
- fail(`Unexpectedly many renders (${count})`);
- }
- } catch (err) {
- testFailures.push(err);
- }
-
- return null;
- }
- }
- );
-
- class ClientSwitcher extends React.Component {
- state = {
- client: client1,
- };
-
- componentDidMount() {
- switchClient = (newClient) => {
- this.setState({ client: newClient });
- };
- }
-
- render() {
- return (
-
-
-
- );
- }
- }
-
- render( );
-
- await waitFor(() => {
- if (testFailures.length > 0) {
- throw testFailures[0];
- }
- expect(count).toBe(12);
- });
- });
-
- it("handles synchronous racecondition with prefilled data from the server", async () => {
- const query: DocumentNode = gql`
- query GetUser($first: Int) {
- user(first: $first) {
- name
- }
- }
- `;
- const variables = { first: 1 };
- type Vars = typeof variables;
- const data2 = { user: { name: "Luke Skywalker" } };
- type Data = typeof data2;
-
- const link = mockSingleLink({
- request: { query, variables },
- result: { data: data2 },
- delay: 10,
- });
- const initialState = {
- apollo: {
- data: {
- ROOT_QUERY: {
- 'user({"first":1})': null,
- },
- },
- },
- };
-
- const client = new ApolloClient({
- link,
- // prefill the store (like SSR would)
- // @see https://github.com/zeit/next.js/blob/master/examples/with-apollo/lib/initApollo.js
- cache: new Cache({ addTypename: false }).restore(initialState),
- });
-
- let count = 0;
- let done = false;
- const Container = graphql(query)(
- class extends React.Component> {
- componentDidMount() {
- void this.props.data!.refetch().then((result) => {
- expect(result.data!.user.name).toBe("Luke Skywalker");
- done = true;
- });
- }
-
- render() {
- count++;
- const user = this.props.data!.user;
- const name = user ? user.name : "";
- if (count === 2) {
- expect(name).toBe("Luke Skywalker");
- }
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- await waitFor(() => expect(done).toBeTruthy());
- });
-
- it("handles asynchronous racecondition with prefilled data from the server", async () => {
- const query: DocumentNode = gql`
- query Q {
- books {
- name
- __typename
- }
- }
- `;
-
- const ssrResult = {
- books: [
- {
- name: "ssrfirst",
- __typename: "Book",
- },
- ],
- };
-
- const result = {
- books: [
- {
- name: "first",
- __typename: "Book",
- },
- ],
- };
-
- const ssrLink = mockSingleLink({
- request: { query } as any,
- result: { data: ssrResult },
- });
-
- const link = mockSingleLink({
- request: { query } as any,
- result: { data: result },
- });
-
- const ssrClient = new ApolloClient({
- cache: new Cache(),
- link: ssrLink,
- });
- await ssrClient.query({
- query,
- variables: {},
- });
- const client = new ApolloClient({
- cache: new Cache().restore(ssrClient.extract()), // --- this is the "SSR" bit
- link,
- });
-
- //try to render the app / call refetch / etc
-
- let done = false;
- let refetched = false;
- const ApolloApp = (
-
-
- {({ loading, data, refetch }: any) => {
- if (!loading) {
- if (!refetched) {
- expect(data.books[0].name).toEqual("ssrfirst");
- //setTimeout allows component to mount, which often happens
- //when waiting ideally we should be able to call refetch
- //immediately However the subscription needs to start before
- //we update the data To get around this issue, we would need
- //to start the subscription before we render to the page. In
- //practice, this seems like an uncommon use case, since the
- //data you get is fresh, so one would wait for an interaction
- setTimeout(() => {
- refetch().then((refetchResult: any) => {
- expect(refetchResult.data.books[0].name).toEqual("first");
- done = true;
- });
- });
- refetched = true;
- } else {
- expect(data.books[0].name).toEqual("first");
- }
- }
- return stub
;
- }}
-
-
- );
-
- expect(render(ApolloApp).container).toMatchSnapshot();
-
- await waitFor(() => {
- expect(done).toBeTruthy();
- });
- });
-});
diff --git a/src/react/hoc/__tests__/queries/loading.test.tsx b/src/react/hoc/__tests__/queries/loading.test.tsx
deleted file mode 100644
index a59cc050d69..00000000000
--- a/src/react/hoc/__tests__/queries/loading.test.tsx
+++ /dev/null
@@ -1,890 +0,0 @@
-import React from "react";
-import { render, waitFor } from "@testing-library/react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import {
- ApolloClient,
- NetworkStatus,
- WatchQueryFetchPolicy,
-} from "../../../../core";
-import { ApolloProvider } from "../../../context";
-import { InMemoryCache as Cache } from "../../../../cache";
-import { itAsync, mockSingleLink } from "../../../../testing";
-import { graphql } from "../../graphql";
-import { ChildProps, DataValue } from "../../types";
-import {
- createRenderStream,
- disableActEnvironment,
-} from "@testing-library/react-render-stream";
-
-describe("[queries] loading", () => {
- // networkStatus / loading
- itAsync(
- "exposes networkStatus as a part of the props api",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const link = mockSingleLink({
- request: { query },
- result: {
- data: { allPeople: { people: [{ name: "Luke Skywalker" }] } },
- },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let done = false;
- const Container = graphql(query, {
- options: { notifyOnNetworkStatusChange: true },
- })(
- class extends React.Component {
- componentDidUpdate() {
- const { data } = this.props;
- expect(data!.networkStatus).toBeTruthy();
- done = true;
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => expect(done).toBeTruthy()).then(resolve, reject);
- }
- );
-
- it("should set the initial networkStatus to 1 (loading)", () => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- @graphql(query, { options: { notifyOnNetworkStatusChange: true } })
- class Container extends React.Component {
- constructor(props: ChildProps) {
- super(props);
- const { networkStatus } = props.data!;
- expect(networkStatus).toBe(1);
- }
-
- render(): React.ReactNode {
- return null;
- }
- }
-
- const { unmount } = render(
-
-
-
- );
-
- unmount();
- });
-
- itAsync(
- "should set the networkStatus to 7 (ready) when the query is loaded",
- (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql(query, {
- options: { notifyOnNetworkStatusChange: true },
- })(
- class extends React.Component {
- componentDidUpdate() {
- expect(this.props.data!.networkStatus).toBe(7);
- done = true;
- }
-
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- }
- );
-
- itAsync(
- "should set the networkStatus to 2 (setVariables) when the query variables are changed",
- (resolve, reject) => {
- let count = 0;
- const query: DocumentNode = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
-
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const variables1 = { first: 1 };
-
- const data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } };
- const variables2 = { first: 2 };
-
- type Data = typeof data1;
- type Vars = typeof variables1;
-
- const link = mockSingleLink(
- { request: { query, variables: variables1 }, result: { data: data1 } },
- { request: { query, variables: variables2 }, result: { data: data2 } }
- );
-
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let done = false;
- const Container = graphql(query, {
- options: (props) => ({
- variables: props,
- notifyOnNetworkStatusChange: true,
- }),
- })(
- class extends React.Component> {
- componentDidUpdate(prevProps: ChildProps) {
- try {
- // variables changed, new query is loading, but old data is still there
- switch (++count) {
- case 1:
- expect(prevProps.data!.loading).toBe(true);
- expect(prevProps.data!.variables).toEqual(variables1);
- expect(prevProps.data!.allPeople).toBe(undefined);
- expect(prevProps.data!.error).toBe(undefined);
- expect(prevProps.data!.networkStatus).toBe(
- NetworkStatus.loading
- );
- expect(this.props.data!.loading).toBe(false);
- expect(this.props.data!.variables).toEqual(variables1);
- expect(this.props.data!.allPeople).toEqual(data1.allPeople);
- expect(this.props.data!.error).toBe(undefined);
- expect(this.props.data!.networkStatus).toBe(
- NetworkStatus.ready
- );
- break;
- case 2:
- expect(prevProps.data!.loading).toBe(false);
- expect(prevProps.data!.variables).toEqual(variables1);
- expect(prevProps.data!.allPeople).toEqual(data1.allPeople);
- expect(prevProps.data!.error).toBe(undefined);
- expect(this.props.data!.loading).toBe(true);
- expect(this.props.data!.variables).toEqual(variables2);
- expect(this.props.data!.allPeople).toBe(undefined);
- expect(this.props.data!.error).toBe(undefined);
- expect(this.props.data!.networkStatus).toBe(
- NetworkStatus.setVariables
- );
- break;
- case 3:
- expect(prevProps.data!.loading).toBe(true);
- expect(prevProps.data!.variables).toEqual(variables2);
- expect(prevProps.data!.allPeople).toBe(undefined);
- expect(prevProps.data!.error).toBe(undefined);
- expect(prevProps.data!.networkStatus).toBe(
- NetworkStatus.setVariables
- );
- expect(this.props.data!.loading).toBe(false);
- expect(this.props.data!.variables).toEqual(variables2);
- expect(this.props.data!.allPeople).toEqual(data2.allPeople);
- expect(this.props.data!.error).toBe(undefined);
- expect(this.props.data!.networkStatus).toBe(
- NetworkStatus.ready
- );
- done = true;
- break;
- }
- } catch (err) {
- reject(err);
- }
- }
- render() {
- return null;
- }
- }
- );
-
- class ChangingProps extends React.Component {
- state = { first: 1 };
-
- componentDidMount() {
- setTimeout(() => {
- this.setState({ first: 2 });
- }, 50);
- }
-
- render() {
- return ;
- }
- }
-
- render(
-
-
-
- );
-
- waitFor(() => expect(done).toBe(true)).then(resolve, reject);
- }
- );
-
- itAsync(
- "resets the loading state after a refetched query",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } };
-
- type Data = typeof data;
-
- const link = mockSingleLink(
- { request: { query }, result: { data } },
- { request: { query }, result: { data: data2 } }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let count = 0;
- const Container = graphql<{}, Data>(query, {
- options: { notifyOnNetworkStatusChange: true },
- })(
- class extends React.Component> {
- componentDidUpdate() {
- const { data } = this.props;
- switch (count++) {
- case 0:
- expect(data!.networkStatus).toBe(7);
- // this isn't reloading fully
- setTimeout(() => {
- void data!.refetch();
- });
- break;
- case 1:
- expect(data!.loading).toBeTruthy();
- expect(data!.networkStatus).toBe(NetworkStatus.refetch);
- expect(data!.allPeople).toEqual(data!.allPeople);
- break;
- case 2:
- expect(data!.loading).toBeFalsy();
- expect(data!.networkStatus).toBe(7);
- expect(data!.allPeople).toEqual(data2.allPeople);
- break;
- default:
- reject(new Error("Too many props updates"));
- }
- }
-
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(count).toBe(3);
- }).then(resolve, reject);
- }
- );
-
- it("correctly sets loading state on remounted network-only query", async () => {
- const query: DocumentNode = gql`
- query pollingPeople {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Darth Skywalker" }] } };
- type Data = typeof data;
-
- const link = mockSingleLink({
- request: { query },
- result: { data },
- newData: () => ({
- data: {
- allPeople: {
- people: [{ name: `Darth Skywalker - ${Math.random()}` }],
- },
- },
- }),
- });
-
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- queryDeduplication: false,
- });
-
- const usedFetchPolicies: WatchQueryFetchPolicy[] = [];
- const Container = graphql<{}, Data>(query, {
- options: {
- fetchPolicy: "network-only",
- nextFetchPolicy(currentFetchPolicy, info) {
- if (info.reason === "variables-changed") {
- return info.initialFetchPolicy;
- }
- usedFetchPolicies.push(currentFetchPolicy);
- if (info.reason === "after-fetch") {
- return "cache-first";
- }
- return currentFetchPolicy;
- },
- },
- })(
- class extends React.Component> {
- render() {
- replaceSnapshot(this.props.data!);
- return null;
- }
- }
- );
-
- const wrapper = ({ children }: { children: React.ReactNode }) => (
- {children}
- );
-
- using _disabledAct = disableActEnvironment();
- const { takeRender, replaceSnapshot, render } = createRenderStream<
- DataValue<{
- allPeople: {
- people: {
- name: string;
- }[];
- };
- }>
- >();
-
- await render( , {
- wrapper,
- });
-
- {
- const { snapshot } = await takeRender();
- expect(snapshot.loading).toBe(true);
- expect(snapshot.allPeople).toBeUndefined();
- }
- {
- const { snapshot } = await takeRender();
- expect(snapshot.loading).toBe(false);
- expect(snapshot.allPeople?.people[0].name).toMatch(/Darth Skywalker - /);
- }
- await render( , {
- wrapper,
- });
- // Loading after remount
- {
- const { snapshot } = await takeRender();
- expect(snapshot.loading).toBe(true);
- expect(snapshot.allPeople).toBeUndefined();
- }
- {
- const { snapshot } = await takeRender();
- // Fetched data loading after remount
- expect(snapshot.loading).toBe(false);
- expect(snapshot.allPeople!.people[0].name).toMatch(/Darth Skywalker - /);
- }
-
- await expect(takeRender).toRenderExactlyTimes(5, {
- timeout: 100,
- });
-
- expect(usedFetchPolicies).toEqual([
- "network-only",
- "network-only",
- "cache-first",
- ]);
- });
-
- itAsync(
- "correctly sets loading state on remounted component with changed variables",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query remount($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
-
- interface Data {
- allPeople: {
- people: { name: string }[];
- };
- }
- const data = { allPeople: null };
- const variables = { first: 1 };
- const variables2 = { first: 2 };
-
- type Vars = typeof variables;
-
- const link = mockSingleLink(
- { request: { query, variables }, result: { data }, delay: 10 },
- {
- request: { query, variables: variables2 },
- result: { data },
- delay: 10,
- }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
- let renderFn: (num: number) => React.ReactElement,
- count = 0;
- const testFailures: any[] = [];
-
- interface Props {
- first: number;
- }
- const Container = graphql(query, {
- options: ({ first }) => ({ variables: { first } }),
- })(
- class extends React.Component> {
- componentDidUpdate() {
- try {
- if (count === 0) {
- // has data
- unmount();
- setTimeout(() => {
- render(renderFn(2));
- }, 5);
- }
-
- if (count === 2) {
- // remounted data after fetch
- expect(this.props.data!.loading).toBeFalsy();
- }
- count++;
- } catch (e) {
- testFailures.push(e);
- }
- }
-
- render() {
- try {
- if (count === 1) {
- expect(this.props.data!.loading).toBeTruthy(); // on remount
- count++;
- }
- } catch (e) {
- testFailures.push(e);
- }
-
- return null;
- }
- }
- );
-
- renderFn = (first: number) => (
-
-
-
- );
- const { unmount } = render(renderFn(1));
- waitFor(() => {
- if (testFailures.length > 0) {
- throw testFailures[0];
- }
- expect(count).toBe(3);
- }).then(resolve, reject);
- }
- );
-
- itAsync(
- "correctly sets loading state on remounted component with changed variables (alt)",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query remount($name: String) {
- allPeople(name: $name) {
- people {
- name
- }
- }
- }
- `;
-
- interface Data {
- allPeople: {
- people: { name: string }[];
- };
- }
- const data = { allPeople: null };
- const variables = { name: "does-not-exist" };
- const variables2 = { name: "nothing-either" };
-
- type Vars = typeof variables;
-
- const link = mockSingleLink(
- { request: { query, variables }, result: { data } },
- {
- request: { query, variables: variables2 },
- result: { data },
- }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let count = 0;
-
- const Container = graphql(query)(
- class extends React.Component> {
- render() {
- const { loading } = this.props.data!;
- if (count === 0) expect(loading).toBeTruthy();
- if (count === 1) {
- expect(loading).toBeFalsy();
- setTimeout(() => {
- unmount();
- render(
-
-
-
- );
- }, 0);
- }
- if (count === 2) expect(loading).toBeTruthy();
- if (count === 3) {
- expect(loading).toBeFalsy();
- }
- count++;
- return null;
- }
- }
- );
-
- const { unmount } = render(
-
-
-
- );
-
- waitFor(() => expect(count).toBe(4)).then(resolve, reject);
- }
- );
-
- itAsync(
- "correctly sets loading state on component with changed variables and unchanged result",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query remount($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
- interface Data {
- allPeople: {
- people: { name: string }[];
- };
- }
-
- const data = { allPeople: null };
- const variables = { first: 1 };
- const variables2 = { first: 2 };
-
- type Vars = typeof variables;
- const link = mockSingleLink(
- { request: { query, variables }, result: { data } },
- {
- request: { query, variables: variables2 },
- result: { data },
- }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
- let count = 0;
-
- interface Props extends Vars {
- setFirst: (first: number) => void;
- }
-
- const connect = (
- component: React.ComponentType<
- React.PropsWithChildren>
- >
- ): React.ComponentType<
- React.PropsWithChildren>
- > => {
- return class extends React.Component<{}, { first: number }> {
- constructor(props: {}) {
- super(props);
-
- this.state = {
- first: 1,
- };
- this.setFirst = this.setFirst.bind(this);
- }
-
- setFirst(first: number) {
- this.setState({ first });
- }
-
- render() {
- return React.createElement(component, {
- first: this.state.first,
- setFirst: this.setFirst,
- });
- }
- };
- };
-
- const Container = connect(
- graphql(query, {
- options: ({ first }) => ({ variables: { first } }),
- })(
- class extends React.Component> {
- render() {
- try {
- switch (count) {
- case 0:
- expect(this.props.data!.loading).toBe(true);
- expect(this.props.data!.allPeople).toBeUndefined();
- break;
- case 1:
- expect(this.props.data!.loading).toBe(false);
- expect(this.props.data!.allPeople).toBe(data.allPeople);
- setTimeout(() => {
- this.props.setFirst(2);
- });
- break;
- case 2:
- expect(this.props.data!.loading).toBe(true); // on variables change
- expect(this.props.data!.allPeople).toBeUndefined();
- break;
- case 4:
- // new data after fetch
- expect(this.props.data!.loading).toBe(false);
- expect(this.props.data!.allPeople).toBe(data.allPeople);
- break;
- }
- } catch (err) {
- reject(err);
- }
-
- count++;
-
- return null;
- }
- }
- )
- );
-
- render(
-
-
-
- );
-
- waitFor(() => expect(count).toBe(4)).then(resolve, reject);
- }
- );
-
- itAsync(
- "correctly sets loading state on component with changed variables, " +
- "unchanged result, and network-only",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query remount($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
- interface Data {
- allPeople: {
- people: { name: string }[];
- };
- }
-
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const variables = { first: 1 };
- const variables2 = { first: 2 };
-
- type Vars = typeof variables;
- const link = mockSingleLink(
- { request: { query, variables }, result: { data }, delay: 10 },
- {
- request: { query, variables: variables2 },
- result: { data },
- delay: 10,
- }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
- let count = 0;
-
- interface Props extends Vars {
- setFirst: (first: number) => void;
- }
-
- const connect = (
- component: React.ComponentType<
- React.PropsWithChildren>
- >
- ): React.ComponentType<
- React.PropsWithChildren>
- > => {
- return class extends React.Component<{}, { first: number }> {
- constructor(props: {}) {
- super(props);
-
- this.state = {
- first: 1,
- };
- this.setFirst = this.setFirst.bind(this);
- }
-
- setFirst(first: number) {
- this.setState({ first });
- }
-
- render() {
- return React.createElement(component, {
- first: this.state.first,
- setFirst: this.setFirst,
- });
- }
- };
- };
-
- const Container = connect(
- graphql(query, {
- options: ({ first }) => ({
- variables: { first },
- fetchPolicy: "network-only",
- }),
- })(
- class extends React.Component> {
- render() {
- const { props } = this;
- try {
- switch (++count) {
- case 1:
- expect(props.data!.loading).toBe(true);
- break;
- case 2:
- expect(props.data!.loading).toBe(false); // has initial data
- expect(props.data!.allPeople).toEqual(data.allPeople);
- setTimeout(() => {
- this.props.setFirst(2);
- });
- break;
- case 3:
- expect(props.data!.loading).toBe(true);
- expect(props.data!.allPeople).toBeUndefined();
- break;
- case 4:
- // new data after fetch
- expect(props.data!.loading).toBe(false);
- expect(props.data!.allPeople).toEqual(data.allPeople);
- break;
- }
- } catch (err) {
- reject(err);
- }
-
- return null;
- }
- }
- )
- );
-
- render(
-
-
-
- );
-
- waitFor(() => expect(count).toBe(4)).then(resolve, reject);
- }
- );
-});
diff --git a/src/react/hoc/__tests__/queries/observableQuery.test.tsx b/src/react/hoc/__tests__/queries/observableQuery.test.tsx
deleted file mode 100644
index c05755b115b..00000000000
--- a/src/react/hoc/__tests__/queries/observableQuery.test.tsx
+++ /dev/null
@@ -1,475 +0,0 @@
-import React from "react";
-import userEvent from "@testing-library/user-event";
-import { render, waitFor, screen } from "@testing-library/react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import { ApolloClient } from "../../../../core";
-import { ApolloProvider } from "../../../context";
-import { InMemoryCache as Cache } from "../../../../cache";
-import { itAsync, mockSingleLink } from "../../../../testing";
-import { graphql } from "../../graphql";
-import { ChildProps } from "../../types";
-
-describe("[queries] observableQuery", () => {
- // observableQuery
- it("will recycle `ObservableQuery`s when re-rendering the entire tree", async () => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data;
-
- const link = mockSingleLink(
- { request: { query }, result: { data } },
- { request: { query }, result: { data } }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let count = 0;
-
- const assert1 = async () => {
- const keys = Array.from(
- ((client as any).queryManager as any).queries.keys()
- );
- await waitFor(() => expect(keys).toEqual(["1"]), { interval: 1 });
- };
-
- const assert2 = async () => {
- const keys = Array.from(
- ((client as any).queryManager as any).queries.keys()
- );
- await waitFor(() => expect(keys).toEqual(["1"]), { interval: 1 });
- };
-
- let done = false;
- const Container = graphql<{}, Data>(query, {
- options: { fetchPolicy: "cache-and-network" },
- })(
- class extends React.Component> {
- async componentDidUpdate() {
- if (count === 2) {
- expect(this.props.data!.loading).toBeFalsy();
- expect(this.props.data!.allPeople).toEqual(data.allPeople);
-
- // ensure first assertion and umount tree
- await assert1();
-
- await userEvent.click(screen.getByText("Break things"));
-
- // ensure cleanup
- await assert2();
- }
-
- if (count === 4) {
- done = true;
- }
- }
-
- render() {
- // during the first mount, the loading prop should be true;
- if (count === 0) {
- expect(this.props.data!.loading).toBeTruthy();
- }
-
- // during the second mount, the loading prop should be false, and data should
- // be present;
- if (count === 3) {
- expect(this.props.data!.loading).toBeFalsy();
- expect(this.props.data!.allPeople).toEqual(data.allPeople);
- }
- count++;
- return null;
- }
- }
- );
-
- class RedirectOnMount extends React.Component<{ onMount: () => void }> {
- componentDidMount() {
- this.props.onMount();
- }
-
- render() {
- return null;
- }
- }
-
- class AppWrapper extends React.Component<{}, { renderRedirect: boolean }> {
- state = {
- renderRedirect: false,
- };
-
- goToRedirect = () => {
- this.setState({ renderRedirect: true });
- };
-
- handleRedirectMount = () => {
- this.setState({ renderRedirect: false });
- };
-
- render() {
- if (this.state.renderRedirect) {
- return ;
- } else {
- return (
-
-
-
- Break things
-
-
- );
- }
- }
- }
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(done).toBeTruthy();
- });
- });
-
- itAsync(
- "will recycle `ObservableQuery`s when re-rendering a portion of the tree but not return stale data if variables don't match",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people($first: Int!) {
- allPeople(first: $first) {
- people {
- name
- friends(id: $first) {
- name
- }
- }
- }
- }
- `;
- const variables1 = { first: 1 };
- const variables2 = { first: 2 };
- const data = {
- allPeople: {
- people: [{ name: "Luke Skywalker", friends: [{ name: "r2d2" }] }],
- },
- };
- const data2 = {
- allPeople: {
- people: [{ name: "Leia Skywalker", friends: [{ name: "luke" }] }],
- },
- };
-
- type Data = typeof data;
- type Vars = typeof variables1;
-
- const link = mockSingleLink(
- { request: { query, variables: variables1 }, result: { data } },
- { request: { query, variables: variables2 }, result: { data: data2 } }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
- let remount: any;
-
- const Container = graphql(query)(
- class extends React.Component> {
- render() {
- try {
- const { variables, loading, allPeople } = this.props.data!;
- // first variable render
- if (variables.first === 1) {
- if (loading) expect(allPeople).toBeUndefined();
- if (!loading) {
- expect(allPeople).toEqual(data.allPeople);
- }
- }
-
- if (variables.first === 2) {
- // second variables render
- if (loading) expect(allPeople).toBeUndefined();
- if (!loading) expect(allPeople).toEqual(data2.allPeople);
- }
- } catch (e) {
- reject(e);
- }
-
- return null;
- }
- }
- );
-
- class Remounter extends React.Component<
- { render: typeof Container },
- { showChildren: boolean; variables: Vars }
- > {
- state = {
- showChildren: true,
- variables: variables1,
- };
-
- componentDidMount() {
- remount = () => {
- this.setState({ showChildren: false }, () => {
- setTimeout(() => {
- this.setState({
- showChildren: true,
- variables: variables2,
- });
- }, 10);
- });
- };
- }
-
- render() {
- if (!this.state.showChildren) return null;
- const Thing = this.props.render;
- return ;
- }
- }
-
- // the initial mount fires off the query
- // the same as episode id = 1
- render(
-
-
-
- );
-
- // after the initial data has been returned
- // the user navigates to a different page
- // but the query is recycled
- let done = false;
- setTimeout(() => {
- // move to the "home" page from the "episode" page
- remount();
- setTimeout(() => {
- // move to a new "epsiode" page
- // epsiode id = 2
- // wait to verify the data isn't stale then end
- done = true;
- }, 20);
- }, 5);
-
- return waitFor(() => expect(done).toBeTruthy()).then(resolve, reject);
- }
- );
-
- it("not overly rerender", async () => {
- const query: DocumentNode = gql`
- query people($first: Int!) {
- allPeople(first: $first) {
- people {
- name
- friends(id: $first) {
- name
- }
- }
- }
- }
- `;
-
- const variables = { first: 1 };
- const data = {
- allPeople: {
- people: [{ name: "Luke Skywalker", friends: [{ name: "r2d2" }] }],
- },
- };
- type Data = typeof data;
- type Vars = typeof variables;
-
- const link = mockSingleLink({
- request: { query, variables },
- result: { data },
- });
-
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
- let remount: any;
-
- let count = 0;
- const Container = graphql(query)(
- class extends React.Component> {
- render() {
- count++;
- const { loading, allPeople } = this.props.data!;
- switch (count) {
- case 1:
- expect(loading).toBe(true);
- break;
- case 2:
- expect(loading).toBe(false);
- expect(allPeople).toEqual(data.allPeople);
- break;
- case 3:
- expect(loading).toBe(false);
- expect(allPeople).toEqual(data.allPeople);
- break;
- default: // Do nothing
- }
-
- return null;
- }
- }
- );
-
- class Remounter extends React.Component<
- { render: typeof Container },
- { showChildren: boolean; variables: Vars }
- > {
- state = {
- showChildren: true,
- variables,
- };
-
- componentDidMount() {
- remount = () => {
- this.setState({ showChildren: false }, () => {
- setTimeout(() => {
- this.setState({ showChildren: true, variables });
- }, 10);
- });
- };
- }
-
- render() {
- if (!this.state.showChildren) return null;
- const Thing = this.props.render;
- return ;
- }
- }
-
- // the initial mount fires off the query
- // the same as episode id = 1
- render(
-
-
-
- );
-
- // after the initial data has been returned
- // the user navigates to a different page
- // but the query is recycled
- let done = false;
- setTimeout(() => {
- // move to the "home" page from the "episode" page
- remount();
- setTimeout(() => {
- // move to the same "episode" page
- // make sure we dont over render
- done = true;
- }, 20);
- }, 5);
-
- await waitFor(() => {
- expect(done).toBeTruthy();
- });
- });
-
- itAsync(
- "does rerender if query returns differnt result",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people($first: Int!) {
- allPeople(first: $first) {
- people {
- name
- friends(id: $first) {
- name
- }
- }
- }
- }
- `;
-
- const variables = { first: 1 };
- const dataOne = {
- allPeople: {
- people: [{ name: "Luke Skywalker", friends: [{ name: "r2d2" }] }],
- },
- };
- const dataTwo = {
- allPeople: {
- people: [
- { name: "Luke Skywalker", friends: [{ name: "Leia Skywalker" }] },
- ],
- },
- };
-
- type Data = typeof dataOne;
- type Vars = typeof variables;
-
- const link = mockSingleLink(
- {
- request: { query, variables },
- result: { data: dataOne },
- },
- {
- request: { query, variables },
- result: { data: dataTwo },
- }
- );
-
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let count = 0;
- const Container = graphql(query)(
- class extends React.Component> {
- render() {
- count++;
- try {
- const { loading, allPeople, refetch } = this.props.data!;
- // first variable render
- if (count === 1) {
- expect(loading).toBe(true);
- }
- if (count === 2) {
- expect(loading).toBe(false);
- expect(allPeople).toEqual(dataOne.allPeople);
- refetch();
- }
- if (count === 3) {
- expect(loading).toBe(false);
- expect(allPeople).toEqual(dataTwo.allPeople);
- }
- if (count > 3) {
- throw new Error("too many renders");
- }
- } catch (e) {
- reject(e);
- }
-
- return null;
- }
- }
- );
-
- // the initial mount fires off the query
- // the same as episode id = 1
- render(
-
-
-
- );
-
- return waitFor(() => expect(count).toBe(3)).then(resolve, reject);
- }
- );
-});
diff --git a/src/react/hoc/__tests__/queries/polling.test.tsx b/src/react/hoc/__tests__/queries/polling.test.tsx
deleted file mode 100644
index 70441733341..00000000000
--- a/src/react/hoc/__tests__/queries/polling.test.tsx
+++ /dev/null
@@ -1,231 +0,0 @@
-import React from "react";
-import { render, waitFor } from "@testing-library/react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import { ApolloClient, ApolloLink } from "../../../../core";
-import { ApolloProvider } from "../../../context";
-import { InMemoryCache as Cache } from "../../../../cache";
-import { itAsync, mockSingleLink } from "../../../../testing";
-import { Observable } from "../../../../utilities";
-import { graphql } from "../../graphql";
-import { ChildProps } from "../../types";
-
-describe("[queries] polling", () => {
- let error: typeof console.error;
-
- beforeEach(() => {
- error = console.error;
- console.error = jest.fn(() => {});
- jest.useRealTimers();
- });
-
- afterEach(() => {
- console.error = error;
- });
-
- // polling
- itAsync("allows a polling query to be created", (resolve, reject) => {
- const POLL_INTERVAL = 5;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } };
- const link = mockSingleLink(
- { request: { query }, result: { data } },
- { request: { query }, result: { data: data2 } },
- { request: { query }, result: { data } }
- );
- const cache = new Cache({ addTypename: false });
- const client = new ApolloClient({
- link,
- cache,
- });
-
- let count = 0;
- const Container = graphql(query, {
- options: () => ({
- pollInterval: POLL_INTERVAL,
- notifyOnNetworkStatusChange: false,
- }),
- })(({ data }) => {
- count++;
- if (count === 4) {
- data!.stopPolling();
- expect(cache.readQuery({ query })).toBeTruthy();
- resolve();
- }
- return null;
- });
-
- render(
-
-
-
- );
-
- waitFor(() => expect(count).toBe(4)).then(resolve, reject);
- });
-
- itAsync(
- "ensures polling respects no-cache fetchPolicy",
- (resolve, reject) => {
- const POLL_INTERVAL = 5;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } };
- const link = mockSingleLink(
- { request: { query }, result: { data } },
- { request: { query }, result: { data: data2 } },
- { request: { query }, result: { data } }
- );
- const cache = new Cache({ addTypename: false });
- const client = new ApolloClient({
- link,
- cache,
- });
-
- let count = 0;
- const Container = graphql(query, {
- options: () => ({
- pollInterval: POLL_INTERVAL,
- notifyOnNetworkStatusChange: false,
- fetchPolicy: "no-cache",
- }),
- })(({ data }) => {
- count++;
- if (count === 4) {
- data!.stopPolling();
- expect(cache.readQuery({ query })).toBeNull();
- resolve();
- }
- return null;
- });
-
- render(
-
-
-
- );
-
- waitFor(() => expect(count).toBe(4)).then(resolve, reject);
- }
- );
-
- const allPeopleQuery: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
-
- const lukeLink = new ApolloLink(
- (operation) =>
- new Observable((observer) => {
- expect(operation.query).toBe(allPeopleQuery);
- observer.next({
- data: {
- allPeople: {
- people: [{ name: "Luke Skywalker" }],
- },
- },
- });
- observer.complete();
- })
- );
-
- itAsync("exposes stopPolling as part of the props api", (resolve, reject) => {
- let done = false;
- const client = new ApolloClient({
- link: lukeLink,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql(allPeopleQuery)(
- class extends React.Component {
- componentDidUpdate() {
- try {
- const { data } = this.props;
- expect(data!.stopPolling).toBeTruthy();
- expect(data!.stopPolling instanceof Function).toBeTruthy();
- expect(data!.stopPolling).not.toThrow();
- done = true;
- } catch (e) {
- reject(e);
- }
- }
- render() {
- return null;
- }
- }
- );
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- });
-
- itAsync(
- "exposes startPolling as part of the props api",
- (resolve, reject) => {
- let done = false;
- const client = new ApolloClient({
- link: lukeLink,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql(allPeopleQuery, {
- options: { pollInterval: 10 },
- })(
- class extends React.Component {
- componentDidUpdate() {
- try {
- const { data } = this.props;
- expect(data!.startPolling).toBeTruthy();
- expect(data!.startPolling instanceof Function).toBeTruthy();
- done = true;
- } catch (e) {
- reject(e);
- }
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- }
- );
-});
diff --git a/src/react/hoc/__tests__/queries/recomposeWithState.tsx b/src/react/hoc/__tests__/queries/recomposeWithState.tsx
deleted file mode 100644
index 68535b077c3..00000000000
--- a/src/react/hoc/__tests__/queries/recomposeWithState.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-// Adapted from v0.30.0 of https://github.com/acdlite/recompose/blob/master/src/packages/recompose/withState.js
-// to avoid incurring an indirect dependency on ua-parser-js via fbjs.
-
-import React, { Component } from "react";
-
-const setStatic =
- (key: string, value: string) => (BaseComponent: React.ComponentClass) => {
- // @ts-ignore
- BaseComponent[key] = value;
- return BaseComponent;
- };
-
-const setDisplayName = (displayName: string) =>
- setStatic("displayName", displayName);
-
-const getDisplayName = (Component: React.ComponentClass) => {
- if (typeof Component === "string") {
- return Component;
- }
-
- if (!Component) {
- return undefined;
- }
-
- return Component.displayName || Component.name || "Component";
-};
-
-const wrapDisplayName = (
- BaseComponent: React.ComponentClass,
- hocName: string
-) => `${hocName}(${getDisplayName(BaseComponent)})`;
-
-export const withState =
- (stateName: string, stateUpdaterName: string, initialState: unknown) =>
- (BaseComponent: React.ComponentClass) => {
- class WithState extends Component<
- Record,
- { stateValue: unknown }
- > {
- state = {
- stateValue:
- typeof initialState === "function" ?
- initialState(this.props)
- : initialState,
- };
-
- updateStateValue = (
- updateFn: (stateValue: unknown) => void,
- callback: () => void
- ) =>
- this.setState(
- ({ stateValue }) => ({
- stateValue:
- typeof updateFn === "function" ? updateFn(stateValue) : updateFn,
- }),
- callback
- );
-
- render() {
- return (
-
- );
- }
- }
-
- if (__DEV__) {
- return setDisplayName(wrapDisplayName(BaseComponent, "withState"))(
- WithState
- );
- }
-
- return WithState;
- };
-
-// Jest complains if modules within __tests__ directories contain no tests.
-describe("withState", () => {
- it("is a function", () => {
- expect(typeof withState).toBe("function");
- });
-});
diff --git a/src/react/hoc/__tests__/queries/reducer.test.tsx b/src/react/hoc/__tests__/queries/reducer.test.tsx
deleted file mode 100644
index 93407151864..00000000000
--- a/src/react/hoc/__tests__/queries/reducer.test.tsx
+++ /dev/null
@@ -1,249 +0,0 @@
-import React from "react";
-import { render, waitFor } from "@testing-library/react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import { ApolloClient } from "../../../../core";
-import { ApolloProvider } from "../../../context";
-import { InMemoryCache as Cache } from "../../../../cache";
-import { itAsync, mockSingleLink } from "../../../../testing";
-import { graphql } from "../../graphql";
-import { DataValue } from "../../types";
-
-describe("[queries] reducer", () => {
- // props reducer
- itAsync("allows custom mapping of a result to props", (resolve, reject) => {
- const query: DocumentNode = gql`
- query thing {
- getThing {
- thing
- }
- }
- `;
- const result = { getThing: { thing: true } };
- const link = mockSingleLink({
- request: { query },
- result: { data: result },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- type Data = typeof result;
- // in case of a skip
- type ChildProps = DataValue;
-
- let count = 0;
- const ContainerWithData = graphql<{}, Data, {}, ChildProps>(query, {
- props: ({ data }) => ({ ...data! }),
- })(({ getThing, loading }) => {
- count++;
- if (count === 1) expect(loading).toBe(true);
- if (count === 2) {
- expect(getThing).toBeDefined();
- }
- return null;
- });
-
- render(
-
-
-
- );
-
- waitFor(() => expect(count).toBe(2)).then(resolve, reject);
- });
-
- itAsync(
- "allows custom mapping of a result to props that includes the passed props",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query thing {
- getThing {
- thing
- }
- }
- `;
- const link = mockSingleLink({
- request: { query },
- result: { data: { getThing: { thing: true } } },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Data {
- getThing: { thing: boolean };
- }
- interface Props {
- sample: number;
- }
-
- type FinalProps = {
- showSpinner: boolean;
- };
-
- let count = 0;
- const ContainerWithData = graphql(query, {
- props: ({ data, ownProps }) => {
- expect(ownProps.sample).toBe(1);
- return { showSpinner: data!.loading };
- },
- })(({ showSpinner }: FinalProps) => {
- if (count === 0) {
- expect(showSpinner).toBeTruthy();
- }
- count += 1;
- return null;
- });
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(count).toBe(2);
- }).then(resolve, reject);
- }
- );
-
- itAsync("allows custom mapping of a result to props 2", (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query thing {
- getThing {
- thing
- }
- }
- `;
- const expectedData = { getThing: { thing: true } };
- const link = mockSingleLink({
- request: { query },
- result: { data: expectedData },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Data {
- getThing: { thing: boolean };
- }
-
- interface FinalProps {
- thingy: { thing: boolean };
- }
-
- const withData = graphql<{}, Data, {}, FinalProps>(query, {
- props: ({ data }) => ({ thingy: data!.getThing! }),
- });
-
- class Container extends React.Component {
- componentDidUpdate() {
- expect(this.props.thingy).toEqual(expectedData.getThing);
- done = true;
- }
- render() {
- return null;
- }
- }
-
- const ContainerWithData = withData(Container);
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- });
-
- itAsync(
- "passes the prior props to the result-props mapper",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query thing {
- getThing {
- thing
- }
- other
- }
- `;
- const expectedData = { getThing: { thing: true }, other: false };
- const expectedDataAfterRefetch = {
- getThing: { thing: true },
- other: true,
- };
- const link = mockSingleLink(
- {
- request: { query },
- result: { data: expectedData },
- },
- {
- request: { query },
- result: { data: expectedDataAfterRefetch },
- }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- type Data = typeof expectedData;
- interface FinalProps {
- wrapper: { thingy: { thing: boolean } };
- refetch: () => any;
- }
-
- const withData = graphql<{}, Data, {}, FinalProps>(query, {
- props: ({ data }, lastProps) => {
- const refetch = data!.refetch!;
- let wrapper = { thingy: data!.getThing! };
-
- // If the current thingy is equal to the last thingy,
- // reuse the wrapper (to preserve referential equality).
- if (lastProps && lastProps.wrapper.thingy === wrapper.thingy) {
- wrapper = lastProps!.wrapper!;
- }
-
- return { wrapper, refetch };
- },
- });
-
- let counter = 0;
- let done = false;
- class Container extends React.Component {
- componentDidUpdate(nextProps: FinalProps) {
- expect(this.props.wrapper.thingy).toEqual(expectedData.getThing);
- if (counter === 1) {
- expect(this.props.wrapper).toEqual(nextProps.wrapper);
- done = true;
- } else {
- counter++;
- this.props.refetch();
- }
- }
- render() {
- return null;
- }
- }
-
- const ContainerWithData = withData(Container);
-
- render(
-
-
-
- );
-
- waitFor(() => expect(done).toBeTruthy()).then(resolve, reject);
- }
- );
-});
diff --git a/src/react/hoc/__tests__/queries/skip.test.tsx b/src/react/hoc/__tests__/queries/skip.test.tsx
deleted file mode 100644
index ee0892df472..00000000000
--- a/src/react/hoc/__tests__/queries/skip.test.tsx
+++ /dev/null
@@ -1,908 +0,0 @@
-import React from "react";
-import { render, waitFor } from "@testing-library/react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import { ApolloClient } from "../../../../core";
-import { ApolloProvider } from "../../../context";
-import { InMemoryCache as Cache } from "../../../../cache";
-import { ApolloLink } from "../../../../link/core";
-import { itAsync, mockSingleLink } from "../../../../testing";
-import { graphql } from "../../graphql";
-import { ChildProps } from "../../types";
-
-describe("[queries] skip", () => {
- itAsync(
- "allows you to skip a query without running it",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
- interface Props {
- skip: boolean;
- }
-
- let queryExecuted = false;
- const Container = graphql(query, {
- skip: ({ skip }) => skip,
- })(
- class extends React.Component> {
- componentDidUpdate() {
- queryExecuted = true;
- }
- render() {
- expect(this.props.data).toBeUndefined();
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- let done = false;
- setTimeout(() => {
- if (!queryExecuted) {
- done = true;
- return;
- }
- reject(new Error("query ran even though skip present"));
- }, 25);
-
- waitFor(() => expect(done).toBeTruthy()).then(resolve, reject);
- }
- );
-
- itAsync(
- "continues to not subscribe to a skipped query when props change",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
-
- const link = new ApolloLink((o, f) => {
- reject(new Error("query ran even though skip present"));
- return f ? f(o) : null;
- }).concat(mockSingleLink());
- // const oldQuery = link.query;
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Props {
- foo: number;
- }
-
- let done = false;
- const Container = graphql(query, { skip: true })(
- class extends React.Component> {
- componentDidUpdate() {
- done = true;
- }
- render() {
- return null;
- }
- }
- );
-
- class Parent extends React.Component<{}, { foo: number }> {
- state = { foo: 42 };
-
- componentDidMount() {
- this.setState({ foo: 43 });
- }
- render() {
- return ;
- }
- }
-
- render(
-
-
-
- );
-
- waitFor(() => expect(done).toBeTruthy()).then(resolve, reject);
- }
- );
-
- itAsync(
- "supports using props for skipping which are used in options",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people($id: ID!) {
- allPeople(first: $id) {
- people {
- id
- }
- }
- }
- `;
-
- const data = {
- allPeople: { people: { id: 1 } },
- };
-
- type Data = typeof data;
-
- const variables = { id: 1 };
- type Vars = typeof variables;
-
- const link = mockSingleLink({
- request: { query, variables },
- result: { data },
- });
-
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let count = 0;
- let renderCount = 0;
-
- interface Props {
- person: { id: number } | null;
- }
- const Container = graphql(query, {
- skip: ({ person }) => !person,
- options: ({ person }) => ({
- variables: {
- id: person!.id,
- },
- }),
- })(
- class extends React.Component> {
- componentDidUpdate() {
- try {
- const { props } = this;
- switch (++count) {
- case 1:
- expect(props.data!.loading).toBe(true);
- break;
- case 2:
- expect(props.data!.loading).toBe(false);
- expect(props.data!.allPeople).toEqual(data.allPeople);
- expect(renderCount).toBe(3);
- break;
- default:
- reject(`Too many renders (${count})`);
- }
- } catch (err) {
- reject(err);
- }
- }
- render() {
- renderCount++;
- return null;
- }
- }
- );
-
- class Parent extends React.Component<
- {},
- { person: { id: number } | null }
- > {
- state = { person: null };
-
- componentDidMount() {
- this.setState({ person: { id: 1 } });
- }
- render() {
- return ;
- }
- }
-
- render(
-
-
-
- );
-
- waitFor(() => expect(count).toBe(2)).then(resolve, reject);
- }
- );
-
- itAsync(
- "doesn't run options or props when skipped, including option.client",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let queryExecuted = false;
- let optionsCalled = false;
-
- interface Props {
- skip: boolean;
- pollInterval?: number;
- }
-
- interface FinalProps {
- pollInterval: number;
- data?: {};
- }
-
- const Container = graphql(query, {
- skip: ({ skip }) => skip,
- options: (props) => {
- optionsCalled = true;
- return {
- pollInterval: props.pollInterval,
- };
- },
- props: (props) => ({
- // intentionally incorrect
- pollInterval: (props as any).willThrowIfAccesed.pollInterval,
- }),
- })(
- class extends React.Component {
- componentDidUpdate() {
- queryExecuted = true;
- }
- render() {
- expect(this.props.data).toBeFalsy();
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- let done = false;
- setTimeout(() => {
- if (!queryExecuted) {
- done = true;
- return;
- }
- if (optionsCalled) {
- reject(new Error("options ran even though skip present"));
- return;
- }
- reject(new Error("query ran even though skip present"));
- }, 25);
-
- waitFor(() => expect(done).toBeTruthy()).then(resolve, reject);
- }
- );
-
- itAsync(
- "doesn't run options or props when skipped even if the component updates",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
-
- const link = mockSingleLink({
- request: { query },
- result: {},
- });
-
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let queryWasSkipped = true;
-
- interface Props {
- foo: string;
- }
-
- let done = false;
- const Container = graphql(query, {
- skip: true,
- options: () => {
- queryWasSkipped = false;
- return {};
- },
- props: () => {
- queryWasSkipped = false;
- return {};
- },
- })(
- class extends React.Component> {
- componentDidUpdate() {
- expect(queryWasSkipped).toBeTruthy();
- done = true;
- }
- render() {
- return null;
- }
- }
- );
-
- class Parent extends React.Component<{}, { foo: string }> {
- state = { foo: "bar" };
- componentDidMount() {
- this.setState({ foo: "baz" });
- }
- render() {
- return ;
- }
- }
-
- render(
-
-
-
- );
-
- waitFor(() => expect(done).toBeTruthy()).then(resolve, reject);
- }
- );
-
- itAsync(
- "allows you to skip a query without running it (alternate syntax)",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let queryExecuted = false;
- const Container = graphql(query, { skip: true })(
- class extends React.Component {
- componentDidUpdate() {
- queryExecuted = true;
- }
- render() {
- expect(this.props.data).toBeFalsy();
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- let done = false;
- setTimeout(() => {
- if (!queryExecuted) {
- done = true;
- return;
- }
- reject(new Error("query ran even though skip present"));
- }, 25);
-
- waitFor(() => expect(done).toBeTruthy()).then(resolve, reject);
- }
- );
-
- // test the case of skip:false -> skip:true -> skip:false to make sure things
- // are cleaned up properly
- itAsync(
- "allows you to skip then unskip a query with top-level syntax",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let hasSkipped = false;
-
- interface Props {
- skip: boolean;
- setSkip: (skip: boolean) => void;
- }
-
- const Container = graphql(query, { skip: ({ skip }) => skip })(
- class extends React.Component> {
- componentDidUpdate(prevProps: ChildProps) {
- if (this.props.skip) {
- hasSkipped = true;
- prevProps.setSkip(false);
- } else {
- if (!hasSkipped) {
- prevProps.setSkip(true);
- }
- }
- }
- render() {
- return null;
- }
- }
- );
-
- class Parent extends React.Component {
- state = { skip: false };
- render() {
- return (
- this.setState({ skip })}
- />
- );
- }
- }
-
- render(
-
-
-
- );
-
- waitFor(() => expect(hasSkipped).toBeTruthy()).then(resolve, reject);
- }
- );
-
- itAsync(
- "allows you to skip then unskip a query with new options (top-level syntax)",
- (resolve, reject) => {
- const query: DocumentNode = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
- const dataOne = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const dataTwo = { allPeople: { people: [{ name: "Leia Skywalker" }] } };
-
- type Data = typeof dataOne;
- type Vars = { first: number };
-
- const link = mockSingleLink(
- {
- request: { query, variables: { first: 1 } },
- result: { data: dataOne },
- },
- {
- request: { query, variables: { first: 2 } },
- result: { data: dataTwo },
- },
- {
- request: { query, variables: { first: 2 } },
- result: { data: dataTwo },
- }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let hasSkipped = false;
-
- interface Props {
- skip: boolean;
- first: number;
- setState: (
- state: Pick<{ skip: boolean; first: number }, K>
- ) => void;
- }
-
- let done = false;
- const Container = graphql(query, {
- skip: ({ skip }) => skip,
- })(
- class extends React.Component> {
- componentDidUpdate(prevProps: ChildProps) {
- if (this.props.skip) {
- hasSkipped = true;
- // change back to skip: false, with a different variable
- prevProps.setState({ skip: false, first: 2 });
- } else {
- if (hasSkipped) {
- if (!this.props.data!.loading) {
- expect(this.props.data!.allPeople).toEqual(dataTwo.allPeople);
- done = true;
- }
- } else {
- expect(this.props.data!.allPeople).toEqual(dataOne.allPeople);
- prevProps.setState({ skip: true });
- }
- }
- }
- render() {
- return null;
- }
- }
- );
-
- class Parent extends React.Component<
- {},
- { skip: boolean; first: number }
- > {
- state = { skip: false, first: 1 };
- render() {
- return (
- this.setState(state)}
- />
- );
- }
- }
-
- render(
-
-
-
- );
-
- waitFor(() => expect(done).toBeTruthy()).then(resolve, reject);
- }
- );
-
- it("allows you to skip then unskip a query with opts syntax", () =>
- new Promise((resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
-
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const nextData = {
- allPeople: { people: [{ name: "Anakin Skywalker" }] },
- };
- const finalData = { allPeople: { people: [{ name: "Darth Vader" }] } };
-
- let ranQuery = 0;
-
- const link = new ApolloLink((o, f) => {
- ranQuery++;
- return f ? f(o) : null;
- }).concat(
- mockSingleLink(
- {
- request: { query },
- result: { data },
- },
- {
- request: { query },
- result: { data: nextData },
- },
- {
- request: { query },
- result: { data: finalData },
- }
- )
- );
-
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- queryDeduplication: false,
- });
-
- let count = 0;
- const Container = graphql(query, {
- options: {
- fetchPolicy: "network-only",
- nextFetchPolicy: "cache-first",
- notifyOnNetworkStatusChange: true,
- },
- skip: ({ skip }) => skip,
- })(
- class extends React.Component {
- render() {
- expect(this.props.data?.error).toBeUndefined();
-
- try {
- switch (++count) {
- case 1:
- expect(this.props.data.loading).toBe(true);
- expect(ranQuery).toBe(0);
- break;
- case 2:
- // The first batch of data is fetched over the network, and
- // verified here, followed by telling the component we want to
- // skip running subsequent queries.
- expect(this.props.data.loading).toBe(false);
- expect(this.props.data.allPeople).toEqual(data.allPeople);
- expect(ranQuery).toBe(1);
- setTimeout(() => {
- this.props.setSkip(true);
- }, 10);
- break;
- case 3:
- // This render is triggered after setting skip to true. Now
- // let's set skip to false to re-trigger the query.
- setTimeout(() => {
- this.props.setSkip(false);
- }, 10);
- expect(this.props.skip).toBe(true);
- expect(this.props.data).toBeUndefined();
- expect(ranQuery).toBe(1);
- break;
- case 4:
- expect(this.props.skip).toBe(false);
- expect(this.props.data!.loading).toBe(false);
- expect(this.props.data.allPeople).toEqual(data.allPeople);
- expect(ranQuery).toBe(2);
- break;
- case 5:
- expect(this.props.skip).toBe(false);
- expect(this.props.data!.loading).toBe(false);
- expect(this.props.data.allPeople).toEqual(nextData.allPeople);
- expect(ranQuery).toBe(2);
- // Since the `nextFetchPolicy` was set to `cache-first`, our
- // query isn't loading as it's able to find the result of the
- // query directly from the cache. Let's trigger a refetch
- // to manually load the next batch of data.
- setTimeout(() => {
- this.props.data.refetch();
- }, 10);
- break;
- case 6:
- expect(this.props.skip).toBe(false);
- expect(ranQuery).toBe(3);
- expect(this.props.data.allPeople).toEqual(nextData.allPeople);
- expect(this.props.data!.loading).toBe(true);
- break;
- case 7:
- // The next batch of data has loaded.
- expect(this.props.skip).toBe(false);
- expect(this.props.data!.loading).toBe(false);
- expect(this.props.data.allPeople).toEqual(
- finalData.allPeople
- );
- expect(ranQuery).toBe(3);
- break;
- default:
- throw new Error(`too many renders (${count})`);
- }
- } catch (err) {
- reject(err);
- }
-
- return null;
- }
- }
- );
-
- class Parent extends React.Component<{}, { skip: boolean }> {
- state = { skip: false };
- render() {
- return (
- this.setState({ skip })}
- />
- );
- }
- }
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(count).toEqual(7);
- }).then(resolve, reject);
- }));
-
- // This test might have value, but is currently broken (the count === 0 test
- // is never hit, for example, because count++ happens the first time before
- // componentDidUpdate is called), so we are skipping it for now.
- it.skip("removes the injected props if skip becomes true", async () => {
- let count = 0;
- const query: DocumentNode = gql`
- query people($first: Int) {
- allPeople(first: $first) {
- people {
- name
- }
- }
- }
- `;
-
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- const variables1 = { first: 1 };
-
- const data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } };
- const variables2 = { first: 2 };
-
- const data3 = { allPeople: { people: [{ name: "Anakin Skywalker" }] } };
- const variables3 = { first: 3 };
-
- type Data = typeof data1;
- type Vars = typeof variables1;
-
- const link = mockSingleLink(
- { request: { query, variables: variables1 }, result: { data: data1 } },
- { request: { query, variables: variables2 }, result: { data: data2 } },
- { request: { query, variables: variables3 }, result: { data: data3 } }
- );
-
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql(query, {
- skip: () => count === 1,
- })(
- class extends React.Component> {
- componentDidUpdate() {
- const { data } = this.props;
- // loading is true, but data still there
- if (count === 0) expect(data!.allPeople).toEqual(data1.allPeople);
- if (count === 1) expect(data).toBeUndefined();
- if (count === 2 && !data!.loading) {
- expect(data!.allPeople).toEqual(data3.allPeople);
- }
- }
- render() {
- return null;
- }
- }
- );
-
- class ChangingProps extends React.Component<{}, { first: number }> {
- state = { first: 1 };
- componentDidMount() {
- setTimeout(() => {
- count++;
- this.setState({ first: 2 });
-
- setTimeout(() => {
- count++;
- this.setState({ first: 3 });
- });
- });
- }
-
- render() {
- return ;
- }
- }
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(count).toEqual(2);
- });
- });
-
- itAsync("allows you to unmount a skipped query", (resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const link = mockSingleLink();
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Props {
- hide: () => void;
- }
-
- let done = false;
- const Container = graphql(query, {
- skip: true,
- })(
- class extends React.Component> {
- componentDidMount() {
- this.props.hide();
- }
- componentWillUnmount() {
- done = true;
- }
- render() {
- return null;
- }
- }
- );
-
- class Hider extends React.Component<{}, { hide: boolean }> {
- state = { hide: false };
- render() {
- if (this.state.hide) {
- return null;
- }
- return this.setState({ hide: true })} />;
- }
- }
-
- render(
-
-
-
- );
-
- waitFor(() => expect(done).toBeTruthy()).then(resolve, reject);
- });
-});
diff --git a/src/react/hoc/__tests__/queries/updateQuery.test.tsx b/src/react/hoc/__tests__/queries/updateQuery.test.tsx
deleted file mode 100644
index b11de81c326..00000000000
--- a/src/react/hoc/__tests__/queries/updateQuery.test.tsx
+++ /dev/null
@@ -1,289 +0,0 @@
-import React from "react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-import { render, waitFor } from "@testing-library/react";
-
-import { ApolloClient } from "../../../../core";
-import { ApolloProvider } from "../../../context";
-import { InMemoryCache as Cache } from "../../../../cache";
-import { itAsync, mockSingleLink } from "../../../../testing";
-import { graphql } from "../../graphql";
-import { ChildProps } from "../../types";
-
-describe("[queries] updateQuery", () => {
- // updateQuery
- itAsync("exposes updateQuery as part of the props api", (resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const link = mockSingleLink({
- request: { query },
- result: { data: { allPeople: { people: [{ name: "Luke Skywalker" }] } } },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let done = false;
- const Container = graphql(query)(
- class extends React.Component {
- componentDidUpdate() {
- const { data } = this.props;
- expect(data!.updateQuery).toBeTruthy();
- expect(data!.updateQuery instanceof Function).toBeTruthy();
- try {
- data!.updateQuery(() => {
- done = true;
- });
- } catch (error) {
- reject(error);
- }
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => expect(done).toBeTruthy()).then(resolve, reject);
- });
-
- itAsync(
- "exposes updateQuery as part of the props api during componentWillMount",
- (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const link = mockSingleLink({
- request: { query },
- result: {
- data: { allPeople: { people: [{ name: "Luke Skywalker" }] } },
- },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql(query)(
- class extends React.Component {
- render() {
- expect(this.props.data!.updateQuery).toBeTruthy();
- expect(
- this.props.data!.updateQuery instanceof Function
- ).toBeTruthy();
- done = true;
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- }
- );
-
- itAsync(
- "updateQuery throws if called before data has returned",
- (resolve, reject) => {
- let renderCount = 0;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const link = mockSingleLink({
- request: { query },
- result: {
- data: { allPeople: { people: [{ name: "Luke Skywalker" }] } },
- },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const Container = graphql(query)(
- class extends React.Component {
- render() {
- expect(this.props.data!.updateQuery).toBeTruthy();
- expect(
- this.props.data!.updateQuery instanceof Function
- ).toBeTruthy();
- try {
- this.props.data!.updateQuery((p) => p);
- } catch (e: any) {
- // TODO: branch never hit in test
- expect(e.toString()).toMatch(
- /ObservableQuery with this id doesn't exist:/
- );
- }
- renderCount += 1;
-
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(renderCount).toBe(2);
- }).then(resolve, reject);
- }
- );
-
- itAsync(
- "allows updating query results after query has finished (early binding)",
- (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data1;
- const data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } };
- const link = mockSingleLink(
- { request: { query }, result: { data: data1 } },
- { request: { query }, result: { data: data2 } }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let isUpdated = false;
- const Container = graphql<{}, Data>(query)(
- class extends React.Component> {
- public updateQuery: any;
- componentDidUpdate() {
- if (isUpdated) {
- expect(this.props.data!.allPeople).toEqual(data2.allPeople);
- done = true;
- return;
- } else {
- isUpdated = true;
- this.updateQuery(() => {
- return data2;
- });
- }
- }
- render() {
- this.updateQuery = this.props.data!.updateQuery;
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- }
- );
-
- itAsync(
- "allows updating query results after query has finished",
- (resolve, reject) => {
- let done = false;
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data1;
-
- const data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } };
- const link = mockSingleLink(
- { request: { query }, result: { data: data1 } },
- { request: { query }, result: { data: data2 } }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let isUpdated = false;
- const Container = graphql<{}, Data>(query)(
- class extends React.Component> {
- componentDidUpdate() {
- if (isUpdated) {
- expect(this.props.data!.allPeople).toEqual(data2.allPeople);
- done = true;
- return;
- } else {
- isUpdated = true;
- this.props.data!.updateQuery(() => {
- return data2;
- });
- }
- }
- render() {
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- waitFor(() => {
- expect(done).toBe(true);
- }).then(resolve, reject);
- }
- );
-});
diff --git a/src/react/hoc/__tests__/shared-operations.test.tsx b/src/react/hoc/__tests__/shared-operations.test.tsx
deleted file mode 100644
index 39c3561998a..00000000000
--- a/src/react/hoc/__tests__/shared-operations.test.tsx
+++ /dev/null
@@ -1,412 +0,0 @@
-import React from "react";
-import { render } from "@testing-library/react";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import { ApolloClient } from "../../../core";
-import { ApolloProvider } from "../../context";
-import { InMemoryCache as Cache } from "../../../cache";
-import { ApolloLink } from "../../../link/core";
-import { itAsync, mockSingleLink } from "../../../testing";
-import { graphql } from "../graphql";
-import { ChildProps, DataValue } from "../types";
-import { withApollo } from "../withApollo";
-
-function compose(...funcs: Function[]) {
- const functions = funcs.reverse();
- return function (...args: any[]) {
- const [firstFunction, ...restFunctions] = functions;
- let result = firstFunction.apply(null, args);
- restFunctions.forEach((fnc) => {
- result = fnc.call(null, result);
- });
- return result;
- };
-}
-
-describe("shared operations", () => {
- describe("withApollo", () => {
- it("passes apollo-client to props", () => {
- const client = new ApolloClient({
- link: new ApolloLink((o, f) => (f ? f(o) : null)),
- cache: new Cache(),
- });
-
- @withApollo
- class ContainerWithData extends React.Component {
- render(): React.ReactNode {
- expect(this.props.client).toEqual(client);
- return null;
- }
- }
-
- render(
-
-
-
- );
- });
- });
-
- it("binds two queries to props", () => {
- const peopleQuery: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const peopleData = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- interface PeopleData {
- allPeople: { people: [{ name: string }] };
- }
-
- const shipsQuery: DocumentNode = gql`
- query ships {
- allships(first: 1) {
- ships {
- name
- }
- }
- }
- `;
- const shipsData = { allships: { ships: [{ name: "Tie Fighter" }] } };
- interface ShipsData {
- allShips: { ships: [{ name: string }] };
- }
-
- const link = mockSingleLink(
- { request: { query: peopleQuery }, result: { data: peopleData } },
- { request: { query: shipsQuery }, result: { data: shipsData } }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface PeopleChildProps {
- people: DataValue;
- }
-
- // Since we want to test decorators usage, and this does not play well with TypeScript,
- // we resort to setting everything as any to avoid type checking.
- const withPeople: any = graphql<{}, PeopleData, {}, PeopleChildProps>(
- peopleQuery,
- {
- name: "people",
- }
- );
-
- interface ShipsChildProps {
- ships: DataValue;
- }
- const withShips: any = graphql<{}, ShipsData, {}, ShipsChildProps>(
- shipsQuery,
- {
- name: "ships",
- }
- );
-
- @withPeople
- @withShips
- class ContainerWithData extends React.Component {
- render() {
- const { people, ships } = this.props;
- expect(people).toBeTruthy();
- expect(people.loading).toBeTruthy();
-
- expect(ships).toBeTruthy();
- expect(ships.loading).toBeTruthy();
- return null;
- }
- }
-
- const { unmount } = render(
-
-
-
- );
- // unmount here or else the query will resolve later and schedule an update that's not wrapped in act.
- unmount();
- });
-
- it("binds two queries to props with different syntax", () => {
- const peopleQuery: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const peopleData = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- interface PeopleData {
- allPeople: { people: [{ name: string }] };
- }
- const shipsQuery: DocumentNode = gql`
- query ships {
- allships(first: 1) {
- ships {
- name
- }
- }
- }
- `;
- const shipsData = { allships: { ships: [{ name: "Tie Fighter" }] } };
- interface ShipsData {
- allShips: { ships: [{ name: string }] };
- }
-
- const link = mockSingleLink(
- { request: { query: peopleQuery }, result: { data: peopleData } },
- { request: { query: shipsQuery }, result: { data: shipsData } }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface PeopleChildProps {
- people: DataValue;
- }
-
- const withPeople = graphql<{}, PeopleData, {}, PeopleChildProps>(
- peopleQuery,
- {
- name: "people",
- }
- );
-
- interface ShipsAndPeopleChildProps extends PeopleChildProps {
- ships: DataValue;
- }
- const withShips = graphql<
- PeopleChildProps,
- ShipsData,
- {},
- ShipsAndPeopleChildProps
- >(shipsQuery, {
- name: "ships",
- });
-
- const ContainerWithData = withPeople(
- withShips((props: ShipsAndPeopleChildProps) => {
- const { people, ships } = props;
- expect(people).toBeTruthy();
- expect(people.loading).toBeTruthy();
-
- expect(ships).toBeTruthy();
- expect(ships.loading).toBeTruthy();
- return null;
- })
- );
-
- const { unmount } = render(
-
-
-
- );
-
- // unmount here or else the query will resolve later and schedule an update that's not wrapped in act.
- unmount();
- });
-
- it("binds two operations to props", () => {
- const peopleQuery: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const peopleData = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
-
- const peopleMutation: DocumentNode = gql`
- mutation addPerson {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const peopleMutationData = {
- allPeople: { people: [{ name: "Leia Skywalker" }] },
- };
-
- const link = mockSingleLink(
- { request: { query: peopleQuery }, result: { data: peopleData } },
- {
- request: { query: peopleMutation },
- result: { data: peopleMutationData },
- }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- const withPeople = graphql(peopleQuery, { name: "people" });
- const withPeopleMutation = graphql(peopleMutation, { name: "addPerson" });
-
- const ContainerWithData = withPeople(
- withPeopleMutation(
- class extends React.Component {
- render() {
- const { people, addPerson } = this.props;
- expect(people).toBeTruthy();
- expect(people.loading).toBeTruthy();
-
- expect(addPerson).toBeTruthy();
- return null;
- }
- }
- )
- );
-
- const { unmount } = render(
-
-
-
- );
-
- // unmount here or else the query will resolve later and schedule an update that's not wrapped in act.
- unmount();
- });
-
- itAsync("allows options to take an object", (resolve, reject) => {
- const query: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } };
- type Data = typeof data;
-
- const link = mockSingleLink({
- request: { query },
- result: { data },
- });
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- let queryExecuted = false;
- const Container = graphql<{}, Data>(query, { skip: true })(
- class extends React.Component> {
- componentDidUpdate() {
- queryExecuted = true;
- }
- render() {
- expect(this.props.data).toBeUndefined();
- return null;
- }
- }
- );
-
- render(
-
-
-
- );
-
- setTimeout(() => {
- if (!queryExecuted) {
- resolve();
- return;
- }
- reject(new Error("query ran even though skip present"));
- }, 25);
- });
-
- describe("compose", () => {
- it("binds two queries to props with different syntax", () => {
- const peopleQuery: DocumentNode = gql`
- query people {
- allPeople(first: 1) {
- people {
- name
- }
- }
- }
- `;
- const peopleData = {
- allPeople: { people: [{ name: "Luke Skywalker" }] },
- };
-
- type PeopleData = typeof peopleData;
-
- const shipsQuery: DocumentNode = gql`
- query ships {
- allships(first: 1) {
- ships {
- name
- }
- }
- }
- `;
- const shipsData = { allships: { ships: [{ name: "Tie Fighter" }] } };
-
- type ShipsData = typeof shipsData;
-
- const link = mockSingleLink(
- { request: { query: peopleQuery }, result: { data: peopleData } },
- { request: { query: shipsQuery }, result: { data: shipsData } }
- );
- const client = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface PeopleChildProps {
- people: DataValue;
- }
-
- interface ShipsAndPeopleChildProps {
- people: DataValue;
- ships: DataValue;
- }
-
- const enhanced = compose(
- graphql<{}, PeopleData, {}, PeopleChildProps>(peopleQuery, {
- name: "people",
- }),
- graphql(
- shipsQuery,
- {
- name: "ships",
- }
- )
- );
-
- const ContainerWithData = enhanced((props: ShipsAndPeopleChildProps) => {
- const { people, ships } = props;
- expect(people).toBeTruthy();
- expect(people.loading).toBeTruthy();
-
- expect(ships).toBeTruthy();
- expect(ships.loading).toBeTruthy();
- return null;
- });
-
- const { unmount } = render(
-
-
-
- );
-
- // unmount here or else the query will resolve later and schedule an update that's not wrapped in act.
- unmount();
- });
- });
-});
diff --git a/src/react/hoc/__tests__/ssr/getDataFromTree.test.tsx b/src/react/hoc/__tests__/ssr/getDataFromTree.test.tsx
deleted file mode 100644
index 70daf897951..00000000000
--- a/src/react/hoc/__tests__/ssr/getDataFromTree.test.tsx
+++ /dev/null
@@ -1,1114 +0,0 @@
-/** @jest-environment node */
-import React from "react";
-import PropTypes from "prop-types";
-import ReactDOM from "react-dom/server";
-import gql from "graphql-tag";
-import { DocumentNode } from "graphql";
-
-import { ApolloClient, TypedDocumentNode } from "../../../../core";
-import { ApolloProvider } from "../../../context";
-import { InMemoryCache as Cache } from "../../../../cache";
-import { itAsync, mockSingleLink } from "../../../../testing";
-import { Query } from "../../../components";
-import { getDataFromTree, getMarkupFromTree } from "../../../ssr";
-import { graphql } from "../../graphql";
-import { ChildProps, DataValue } from "../../types";
-
-describe("SSR", () => {
- describe("`getDataFromTree`", () => {
- const consoleWarn = console.warn;
- beforeAll(() => {
- console.warn = () => null;
- });
-
- afterAll(() => {
- console.warn = consoleWarn;
- });
-
- it("should run through all of the queries that want SSR", async () => {
- const query = gql`
- {
- currentUser {
- firstName
- }
- }
- `;
- const data1 = { currentUser: { firstName: "James" } };
- const link = mockSingleLink({
- request: { query },
- result: { data: data1 },
- });
- const apolloClient = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Props {}
- interface Data {
- currentUser: {
- firstName: string;
- };
- }
- const WrappedElement = graphql(query)(
- ({ data }: ChildProps) => (
-
- {!data || data.loading || !data.currentUser ?
- "loading"
- : data.currentUser.firstName}
-
- )
- );
-
- const app = (
-
-
-
- );
-
- await getDataFromTree(app).then((markup) => {
- expect(markup).toMatch(/James/);
- });
-
- await getMarkupFromTree({
- tree: app,
- renderFunction: ReactDOM.renderToString,
- }).then((markup) => {
- expect(markup).toMatch(/James/);
- });
- });
-
- it("should allow network-only fetchPolicy as an option and still render prefetched data", () => {
- const query = gql`
- {
- currentUser {
- firstName
- }
- }
- `;
- const link = mockSingleLink({
- request: { query },
- result: { data: { currentUser: { firstName: "James" } } },
- });
- const apolloClient = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- ssrMode: true,
- });
-
- interface Props {}
- interface Data {
- currentUser: {
- firstName: string;
- };
- }
- const WrappedElement = graphql(query, {
- options: { fetchPolicy: "network-only" },
- })(({ data }: ChildProps) => (
-
- {!data || data.loading || !data.currentUser ?
- "loading"
- : data.currentUser.firstName}
-
- ));
-
- const app = (
-
-
-
- );
-
- return getDataFromTree(app).then((markup) => {
- expect(markup).toMatch(/James/);
- });
- });
-
- it("should allow cache-and-network fetchPolicy as an option and still render prefetched data", () => {
- const query = gql`
- {
- currentUser {
- firstName
- }
- }
- `;
- const link = mockSingleLink({
- request: { query },
- result: { data: { currentUser: { firstName: "James" } } },
- });
- const apolloClient = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Props {}
- interface Data {
- currentUser: {
- firstName: string;
- };
- }
- const WrappedElement = graphql(query, {
- options: { fetchPolicy: "cache-and-network" },
- })(({ data }: ChildProps) => (
-
- {data && data.currentUser ? data.currentUser.firstName : "loading"}
-
- ));
-
- const app = (
-
-
-
- );
-
- return getDataFromTree(app).then((markup) => {
- expect(markup).toMatch(/James/);
- });
- });
-
- it("should pick up queries deep in the render tree", () => {
- const query = gql`
- {
- currentUser {
- firstName
- }
- }
- `;
- const link = mockSingleLink({
- request: { query },
- result: { data: { currentUser: { firstName: "James" } } },
- });
- const apolloClient = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Props {}
- interface Data {
- currentUser: {
- firstName: string;
- };
- }
-
- const WrappedElement = graphql(query)(
- ({ data }: ChildProps) => (
-
- {!data || data.loading || !data.currentUser ?
- "loading"
- : data.currentUser.firstName}
-
- )
- );
-
- const Page = () => (
-
- );
-
- const app = (
-
-
-
- );
-
- return getDataFromTree(app).then((markup) => {
- expect(markup).toMatch(/James/);
- });
- });
-
- it("should handle nested queries that depend on each other", () => {
- const idQuery: DocumentNode = gql`
- {
- currentUser {
- id
- }
- }
- `;
- const idData = { currentUser: { id: "1234" } };
- const userQuery: DocumentNode = gql`
- query getUser($id: String) {
- user(id: $id) {
- firstName
- }
- }
- `;
- const variables = { id: "1234" };
- const userData = { user: { firstName: "James" } };
- const link = mockSingleLink(
- { request: { query: idQuery }, result: { data: idData } },
- {
- request: { query: userQuery, variables },
- result: { data: userData },
- }
- );
- const apolloClient = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Props {}
- interface IdQueryData {
- currentUser: {
- id: string;
- };
- }
-
- interface UserQueryData {
- user: {
- firstName: string;
- };
- }
-
- interface UserQueryVariables {
- id: string;
- }
-
- type WithIdChildProps = ChildProps;
- const withId = graphql(idQuery);
-
- const withUser = graphql<
- WithIdChildProps,
- UserQueryData,
- UserQueryVariables
- >(userQuery, {
- skip: ({ data }) => data!.loading,
- options: ({ data }) => ({
- variables: { id: data!.currentUser!.id },
- }),
- });
- const Component: React.FunctionComponent<
- React.PropsWithChildren>
- > = ({ data }) => (
-
- {!data || data.loading || !data.user ?
- "loading"
- : data.user.firstName}
-
- );
-
- const WrappedComponent = withId(withUser(Component));
-
- const app = (
-
-
-
- );
-
- return getDataFromTree(app).then((markup) => {
- expect(markup).toMatch(/James/);
- });
- });
-
- it.skip("should return the first of multiple errors thrown by nested wrapped components", () => {
- const lastNameQuery = gql`
- {
- currentUser {
- lastName
- }
- }
- `;
- interface LastNameData {
- currentUser: {
- lastName: string;
- };
- }
- const firstNameQuery = gql`
- {
- currentUser {
- firstName
- }
- }
- `;
-
- const link = mockSingleLink(
- {
- request: { query: lastNameQuery },
- result: {
- data: {
- currentUser: {
- lastName: "Tester",
- },
- },
- },
- },
- {
- request: { query: firstNameQuery },
- result: {
- data: {
- currentUser: {
- firstName: "James",
- },
- },
- },
- }
- );
- const apolloClient = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Props {}
-
- type WithLastNameProps = ChildProps;
- const withLastName = graphql(lastNameQuery);
-
- const fooError = new Error("foo");
- const BorkedComponent = () => {
- throw fooError;
- };
-
- const WrappedBorkedComponent = withLastName(BorkedComponent);
-
- const ContainerComponent: React.FunctionComponent<
- React.PropsWithChildren>
- > = ({ data }) => (
-
- {!data || data.loading || !data.currentUser ?
- "loading"
- : data.currentUser.lastName}
-
-
-
- );
-
- const withFirstName = graphql(firstNameQuery);
-
- const WrappedContainerComponent = withFirstName(ContainerComponent);
-
- const app = (
-
-
-
- );
-
- return getDataFromTree(app).then(
- () => {
- throw new Error("Should have thrown an error");
- },
- (e) => {
- expect(e.toString()).toEqual("Error: foo");
- expect(e).toBe(fooError);
- }
- );
- });
-
- it("should handle errors thrown by queries", () => {
- const query = gql`
- {
- currentUser {
- firstName
- }
- }
- `;
- const link = mockSingleLink({
- request: { query },
- error: new Error("Failed to fetch"),
- });
- const apolloClient = new ApolloClient({
- link,
- cache: new Cache({ addTypename: false }),
- });
-
- interface Props {}
- interface Data {
- currentUser: {
- firstName: string;
- };
- }
- const WrappedElement = graphql(query)(
- ({ data }: ChildProps) => (
-