Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update readme and storybook #27

Merged
merged 3 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ jobs:
version: 9
run_install: true

# - name: Create release PR or publish to npm + GitHub
# id: changesets
# if: steps.check_files.outputs.files_exists == 'false'
# uses: changesets/action@v1
# with:
# version: pnpm run changeset-version
# publish: pnpm run changeset-publish
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create release PR or publish to npm + GitHub
id: changesets
if: steps.check_files.outputs.files_exists == 'false'
uses: changesets/action@v1
with:
version: pnpm run changeset-version
publish: pnpm run changeset-publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
16 changes: 11 additions & 5 deletions .storybook/stories/ApolloComponent.stories.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import type { Meta, StoryObj } from "@storybook/react";
import { within, expect, waitFor } from "@storybook/test";
import ApolloComponent from "./components/apollo-client/ApolloComponent.js";
import {
ApolloApp,
ApolloAppWithDefer as AppWithDefer,
} from "./components/apollo-client/ApolloComponent.js";
import { createHandler } from "../../src/handlers";
import { schemaWithMocks } from "../../src/__tests__/mocks/handlers";
import { Canvas } from "@storybook/blocks";

const { handler } = createHandler(schemaWithMocks);

const meta = {
title: "Example/ApolloComponent",
component: ApolloComponent,
title: "Example/Apollo",
component: ApolloApp,
parameters: {
layout: "centered",
msw: {
Expand All @@ -17,13 +21,15 @@ const meta = {
},
},
},
} satisfies Meta<typeof ApolloComponent>;
} satisfies Meta<typeof ApolloApp>;

export default meta;

export { AppWithDefer };

type Story = StoryObj<typeof meta>;

export const Primary: Story = {
export const App: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(
Expand Down
15 changes: 10 additions & 5 deletions .storybook/stories/RelayComponent.stories.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import type { Meta, StoryObj } from "@storybook/react";
import { within, expect, waitFor } from "@storybook/test";
import RelayComponent from "./components/relay/RelayComponent.js";
import {
RelayApp,
RelayAppWithDefer as AppWithDefer,
} from "./components/relay/RelayComponent.js";
import { createHandler } from "../../src/handlers";
import { schemaWithMocks } from "../../src/__tests__/mocks/handlers";

const { handler } = createHandler(schemaWithMocks);

const meta = {
title: "Example/RelayComponent",
component: RelayComponent,
title: "Example/Relay",
component: RelayApp,
parameters: {
layout: "centered",
msw: {
Expand All @@ -17,13 +20,15 @@ const meta = {
},
},
},
} satisfies Meta<typeof RelayComponent>;
} satisfies Meta<typeof RelayApp>;

export default meta;

export { AppWithDefer };

type Story = StoryObj<typeof meta>;

export const Primary: Story = {
export const App: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(
Expand Down
73 changes: 70 additions & 3 deletions .storybook/stories/Welcome.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,75 @@ import { Meta } from "@storybook/blocks";

<Meta title="GraphQL Testing Library" />

# GraphQL Testing Library
<div align="center">
<h1>GraphQL Testing Library</h1>

GraphQL Testing Library provides generic utilities for creating [Mock Service Worker](https://mswjs.io/) handlers for any GraphQL API. MSW is also the [Testing Library-recommended](https://testing-library.com/docs/react-testing-library/example-intro/#full-example) way to declaratively mock API communication in your tests without stubbing `window.fetch`.
{/* <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> */}

> This project is not affiliated with the ["Testing Library"](https://github.com/testing-library) ecosystem that this project is clearly inspired from.
<p>Testing utilities that encourage good practices for apps built with GraphQL.</p>

</div>
<hr />

**GraphQL Testing Library** provides utilities that make it easy to generate [Mock Service Worker](https://mswjs.io/) handlers for any GraphQL API.

MSW is the [Testing Library-recommended](https://testing-library.com/docs/react-testing-library/example-intro/#full-example) way to declaratively mock API communication in your tests without stubbing `window.fetch`.

This library currently supports incremental delivery features `@defer` and `@stream` out of the box, with plans to support subscriptions over multipart HTTP as well as other transports such as WebSockets, [currently in beta in MSW](https://github.com/mswjs/msw/discussions/2010).

> 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:

```
npm install --save-dev @apollo/graphql-testing-library msw graphql
pnpm add --save-dev @apollo/graphql-testing-library msw graphql
yarn add --dev @apollo/graphql-testing-library msw graphql
bun add --dev @apollo/graphql-testing-library msw graphql
```

## Usage

### `createHandler`

```typescript
import { createHandler } from "@apollo/graphql-testing-library";

// We suggest using @graphql-tools/mock and @graphql-tools/schema
// to create a schema with mock resolvers.
// See https://the-guild.dev/graphql/tools/docs/mocking for more info.
import { addMocksToSchema } from "@graphql-tools/mock";
import { makeExecutableSchema } from "@graphql-tools/schema";
import typeDefs from "./schema.graphql";

// Create an executable schema
const schema = makeExecutableSchema({ typeDefs });

// Add mock resolvers
const schemaWithMocks = addMocksToSchema({
schema,
resolvers: {
Query: {
products: () =>
Array.from({ length: 5 }, (_element, id) => ({
id: `product-${id}`,
})),
},
},
});

// `createHandler` returns an object with `handler` and `replaceSchema`
// functions: `handler` is a MSW handler that will intercept all GraphQL
// 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 },
});
```
74 changes: 65 additions & 9 deletions .storybook/stories/components/apollo-client/ApolloComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Suspense } from "react";
import { Suspense, type ReactNode } from "react";
import type { TypedDocumentNode } from "@apollo/client";
import {
gql,
Expand All @@ -9,8 +9,8 @@ import {
HttpLink,
useSuspenseQuery,
} from "@apollo/client";
import { Container } from "../Container.js";
import { Product, Reviews } from "../Product.js";
import { Container } from "../Container.js";

const httpLink = new HttpLink({
uri: "https://main--hack-the-e-commerce.apollographos.net/graphql",
Expand All @@ -25,14 +25,38 @@ export const makeClient = () =>

export const client = makeClient();

const QUERY: TypedDocumentNode<{
const APP_QUERY: TypedDocumentNode<{
products: {
id: string;
title: string;
mediaUrl: string;
description: string;
reviews: Array<{ rating: number; id: string; content: string }>;
}[];
}> = gql`
query AppQuery {
products {
id
reviews {
id
rating
content
}
title
mediaUrl
description
}
}
`;

const APP_QUERY_WITH_DEFER: TypedDocumentNode<{
products: {
id: string;
title: string;
mediaUrl: string;
description: string;
reviews?: Array<{ rating: number; id: string; content: string }>;
}[];
}> = gql`
query AppQuery {
products {
Expand All @@ -51,18 +75,34 @@ const QUERY: TypedDocumentNode<{
}
`;

export default function App() {
function Wrapper({ children }: { children: ReactNode }) {
return (
<ApolloProvider client={client}>
<Suspense fallback={<h1>Loading...</h1>}>
<Main />
</Suspense>
<Suspense fallback={<h1>Loading...</h1>}>{children}</Suspense>
</ApolloProvider>
);
}

export function Main() {
const { data } = useSuspenseQuery(QUERY);
export function ApolloApp() {
return (
<Wrapper>
<App />
</Wrapper>
);
}

export function ApolloAppWithDefer() {
return (
<Wrapper>
<AppWithDefer />
</Wrapper>
);
}

export function App() {
// Use useSuspenseQuery here because we want to demo the loading experience
// with/without defer.
const { data } = useSuspenseQuery(APP_QUERY);

return (
<Container>
Expand All @@ -74,3 +114,19 @@ export function Main() {
</Container>
);
}

export function AppWithDefer() {
// Use useSuspenseQuery here because we want to demo the loading experience
// with/without defer.
const { data } = useSuspenseQuery(APP_QUERY_WITH_DEFER);

return (
<Container>
{data.products.map((product) => (
<Product key={product.id} product={product}>
<Reviews reviews={product.reviews || []} />
</Product>
))}
</Container>
);
}
Loading