Skip to content

Commit

Permalink
Support infinite loading (#57)
Browse files Browse the repository at this point in the history
  • Loading branch information
alessbell authored Jul 16, 2024
1 parent e2f1637 commit 47bf677
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 74 deletions.
5 changes: 5 additions & 0 deletions .changeset/rotten-rockets-deny.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/graphql-testing-library": patch
---

Adds support for MSW's delay API and infinite loading states
14 changes: 7 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
cache: "pnpm"

- name: Get pnpm store directory
shell: bash
Expand Down Expand Up @@ -59,7 +59,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
cache: "pnpm"

- name: Install dependencies
run: pnpm install
Expand All @@ -85,7 +85,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
cache: "pnpm"

- name: Install dependencies
run: pnpm install
Expand Down Expand Up @@ -126,11 +126,11 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
cache: "pnpm"

- name: Install dependencies
run: pnpm install

- name: Lint
run: pnpm run lint

Expand All @@ -152,10 +152,10 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
cache: "pnpm"

- name: Install dependencies
run: pnpm install

- name: Check types
run: pnpm run type-check
run: pnpm run type-check
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ temp/
*storybook.log

# output of storybook-build
storybook-static
storybook-static

# jest coverage
coverage
19 changes: 11 additions & 8 deletions .storybook/stories/Welcome.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ import { Meta } from "@storybook/blocks";
<div align="center">
<h1>GraphQL Testing Library</h1>

{/* <a href="https://www.apollographql.com/"><img src="https://raw.githubusercontent.com/apollographql/apollo-client-devtools/main/assets/apollo-wordmark.svg" height="100" alt="Apollo Client" /></a> */}
{/* <a href="https://www.apollographql.com/"><img src="https://raw.githubusercontent.com/apollographql/apollo-client-devtools/main/assets/apollo-wordmark.svg" height="100" alt="Apollo Client" /></a> */}

<p>Testing utilities that encourage good practices for apps built with GraphQL.</p>
{" "}

<p>
Testing utilities that encourage good practices for apps built with GraphQL.
</p>

</div>
<hr />
Expand All @@ -20,7 +24,6 @@ This library currently supports incremental delivery features `@defer` and `@str

> This project is not affiliated with the ["Testing Library"](https://github.com/testing-library) ecosystem that inspired it. We're just fans :)

## Installation

This library has `peerDependencies` listings for `msw` at `^2.0.0` and `graphql` at `^15.0.0 || ^16.0.0`. Install them along with this library using your preferred package manager:
Expand Down Expand Up @@ -67,10 +70,10 @@ const schemaWithMocks = addMocksToSchema({
// operations, and `replaceSchema` allows you to replace the mock schema
// the `handler` use to resolve requests against.
const { handler, replaceSchema } = createHandler(schemaWithMocks, {
// It accepts a config object as the second argument where you can specify a
// delay min and max, which will add random delays to your tests within the /
// threshold to simulate a real network connection.
// Default: delay: { min: 300, max: 300 }
delay: { min: 200, max: 500 },
// It accepts a config object as the second argument where you can specify a
// delay duration, which uses MSW's delay API:
// https://mswjs.io/docs/api/delay
// Default: "real"
delay: number | "infinite" | "real",
});
```
4 changes: 3 additions & 1 deletion .storybook/stories/components/Product.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ function Product({
</a>
</h3>
</div>
<p className="text-sm font-medium text-gray-900">{children}</p>
<p data-testid="rating" className="text-sm font-medium text-gray-900">
{children}
</p>
</div>
</div>
);
Expand Down
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ This library currently supports incremental delivery features `@defer` and `@str

> This project is not affiliated with the ["Testing Library"](https://github.com/testing-library) ecosystem that inspired it. We're just fans :)

## Installation

This library has `peerDependencies` listings for `msw` at `^2.0.0` and `graphql` at `^15.0.0 || ^16.0.0`. Install them along with this library using your preferred package manager:
Expand Down Expand Up @@ -63,10 +62,10 @@ const schemaWithMocks = addMocksToSchema({
// operations, and `replaceSchema` allows you to replace the mock schema
// the `handler` use to resolve requests against.
const { handler, replaceSchema } = createHandler(schemaWithMocks, {
// It accepts a config object as the second argument where you can specify a
// delay min and max, which will add random delays to your tests within the /
// threshold to simulate a real network connection.
// Default: delay: { min: 300, max: 300 }
delay: { min: 200, max: 500 },
// It accepts a config object as the second argument where you can specify a
// delay duration, which uses MSW's delay API:
// https://mswjs.io/docs/api/delay
// Default: "real"
delay: number | "infinite" | "real",
});
```
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@
},
"dependencies": {
"@graphql-tools/executor": "^1.2.7",
"graphql-tag": "^2.12.6"
"graphql-tag": "^2.12.6",
"is-node-process": "^1.2.0"
},
"peerDependencies": {
"graphql": "^15.0.0 || ^16.0.0",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

100 changes: 75 additions & 25 deletions src/__tests__/handlers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import {
import { addMocksToSchema } from "@graphql-tools/mock";
import { makeExecutableSchema } from "@graphql-tools/schema";
import graphqlSchema from "../../.storybook/stories/components/relay/schema.graphql";
import { replaceSchema } from "./mocks/handlers.js";
import { replaceSchema, replaceDelay } from "./mocks/handlers.js";
import { describe, expect, it } from "@jest/globals";
import { render, screen, waitFor } from "@testing-library/react";

describe("integration tests", () => {
it("runs a test", async () => {
it("uses the initial mock schema set in the handler passed to setupServer by default", async () => {
const client = makeClient();

render(
Expand All @@ -23,26 +23,29 @@ describe("integration tests", () => {
</ApolloProvider>
);

// The app kicks off the request and we see the initial loading indicator...
await waitFor(() =>
expect(
screen.getByRole("heading", { name: /loading/i })
).toHaveTextContent("Loading...")
);

await waitFor(
() =>
expect(
screen.getByRole("heading", { name: /customers/i })
).toHaveTextContent("Customers also purchased"),
{ timeout: 2000 }
// Once the screen unsuspends, we have rendered all data from both the
// first and second chunk.
await waitFor(() =>
expect(
screen.getByRole("heading", { name: /customers/i })
).toHaveTextContent("Customers also purchased")
);

await waitFor(() => {
expect(screen.getByText(/beanie/i)).toBeInTheDocument();
});
// The default "real" delay in a Node process is 1ms, so here we expect the
// two deferred chunks to be batched into a single render.
expect(screen.getAllByTestId(/rating/i)[0]).not.toHaveTextContent("-");
expect(screen.getByText(/beanie/i)).toBeInTheDocument();
});
it("runs a second test", async () => {
// Make a GraphQL schema with no resolvers

it("can set a new schema via replaceSchema", async () => {
// Create an executable GraphQL schema with no resolvers
const schema = makeExecutableSchema({ typeDefs: graphqlSchema });

// Create a new schema with mocks
Expand All @@ -57,7 +60,7 @@ describe("integration tests", () => {
reviews: [
{
id: `review-${id}`,
rating: id.toFixed(1),
rating: id,
},
],
}));
Expand All @@ -66,7 +69,7 @@ describe("integration tests", () => {
},
});

replaceSchema(schemaWithMocks);
using _restore = replaceSchema(schemaWithMocks);

const client = makeClient();

Expand All @@ -78,25 +81,72 @@ describe("integration tests", () => {
</ApolloProvider>
);

// The app kicks off the request and we see the initial loading indicator...
await waitFor(() =>
expect(
screen.getByRole("heading", { name: /loading/i })
).toHaveTextContent("Loading...")
);

await waitFor(
() =>
expect(
screen.getByRole("heading", { name: /customers/i })
).toHaveTextContent("Customers also purchased"),
{ timeout: 2000 }
// Once the screen unsuspends, we have rendered all data from both the
// first and second chunk.
await waitFor(() =>
expect(
screen.getByRole("heading", { name: /customers/i })
).toHaveTextContent("Customers also purchased")
);

expect(screen.getByText(/foo bar 1/i)).toBeInTheDocument();

// This "5/5" review value was set when we called `replaceSchema` with a new
// executable schema in this test.
expect(screen.getByText(/5\/5/i)).toBeInTheDocument();
});

it("can set a new delay via replaceDelay", async () => {
// In this test we set a new delay value via `replaceDelay` which is used to
// simulate network latency.
// Usually, in Jest tests we want this to be 1ms (the default in Node
// processes) but in certain tests we may want to validate that e.g.
// a multipart response activates Suspense boundaries.

// Also, by creating a disposable via `using`, the delay gets
// automatically restored to its previous value at the end of the test.
using _restore = replaceDelay(500);

const client = makeClient();

render(
<ApolloProvider client={client}>
<Suspense fallback={<h1>Loading...</h1>}>
<AppWithDefer />
</Suspense>
</ApolloProvider>
);

// The app kicks off the request and we see the initial loading indicator...
await waitFor(() =>
expect(
screen.getByRole("heading", { name: /loading/i })
).toHaveTextContent("Loading...")
);

await waitFor(() =>
expect(
screen.getByRole("heading", { name: /customers/i })
).toHaveTextContent("Customers also purchased")
);

// Since our renders are no longer batched due to the 500ms delay, we will
// now see a loading state for the reviews, indicated by a "-" placeholder.
expect(screen.getAllByTestId(/rating/i)[0]).toHaveTextContent("-");
expect(screen.getByText(/beanie/i)).toBeInTheDocument();

// And one final render once the second chunk resolves after the delay and
// the reviews can be displayed. Note that the original schema has been
// restored.
await waitFor(() => {
expect(screen.getByText(/foo bar 1/i)).toBeInTheDocument();
});
await waitFor(() => {
expect(screen.getByText(/5\/5/i)).toBeInTheDocument();
expect(screen.getByText(/10\/5/i)).toBeInTheDocument();
});
});
});
10 changes: 8 additions & 2 deletions src/__tests__/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@ const schemaWithMocks = addMocksToSchema({
id,
title: products[id],
mediaUrl: `https://storage.googleapis.com/hack-the-supergraph/apollo-${products[id]}.jpg`,
reviews: [
{
id: `review-${id}`,
rating: id * 2,
},
],
})),
},
},
});

const { handler, replaceSchema } = createHandler(schemaWithMocks);
const { handler, replaceSchema, replaceDelay } = createHandler(schemaWithMocks);

const handlers = [handler];

export { replaceSchema, handlers, schemaWithMocks };
export { replaceSchema, replaceDelay, handlers, schemaWithMocks };
Loading

0 comments on commit 47bf677

Please sign in to comment.