Skip to content

Commit

Permalink
0.9.0 (5) - Add integration test for jest (#220)
Browse files Browse the repository at this point in the history
* add a "jest" integration test

* split up into "sub-monorepo"

* more logs

* move around monorepo link

* build deps first

* allow updating checksum

* ignore checksums in integration tests

* mark pattern

* call for linux, not osx

* rename test runs

* fixup

* 0.9.0 (4) -  Add integration test for vitejs + streaming (#198)

* rename wrapped classes to original name, also export in RSC

* fix up test

* fixup build types

* comment shape

* adjust type resolution

* fixup

* adjust package shapes

* bundling adjustment: no `/index.js` in CJS

* check "package shapes"

* revert changes to examples/integration tests

* reset README changes

* add description to helper scripts

* wrap hook functionality from ApolloClient instance

* move wrappers to `QueryManager`

* update version

* remove hook exports from `@apollo/client-react-streaming`

* run test-bundle in CI

* drop hook exports

* move "integration-test" into "integration-test/nextjs"

* initialize basic vite react-ssr project

* apply prettier

* [WIP] try to set up a streaming server

* fix hydration, force faster flush

* experimental-react demo working

* add delay

* also works with prod build

* update lockfile

* global react version

* simplify a bit

* move experimental detail out to `WrappedApolloProvider`

* adjustments

* remove `@apollo/client-react-streaming/experimental-react-transport`

* alternative installation

* alternative approach

* fix non-failing tests

* fix bug that prevented rerunning hydrated queries if initialized too late

* adjust script

* more packaging foo

* fixup app rendering

* playwright test

* split up

* copy experimental to vite-streaming

* adjust types

* remove `Writable` from experimental example

* vite-streaming

* remove insertions after last flush

* add pipeTrough

* learnings from experimental-react

* add steps to workflow

* build only libraries in test preparation

* review feedback

* small fixup

* Update integration-test/jest/package.json

Co-authored-by: Jerel Miller <jerelmiller@gmail.com>

* Update integration-test/jest/src/App.jsx

---------

Co-authored-by: Jerel Miller <jerelmiller@gmail.com>
  • Loading branch information
phryneas and jerelmiller authored Mar 6, 2024
1 parent 7909434 commit 31cc402
Show file tree
Hide file tree
Showing 17 changed files with 8,710 additions and 491 deletions.
35 changes: 25 additions & 10 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ on:
workflow_dispatch:
jobs:
test:
name: Run unit tests
name: Unit Tests
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
runs-on: ubuntu-latest
strategy:
Expand All @@ -32,7 +32,7 @@ jobs:
- run: yarn workspaces foreach --all --include "@apollo/*" run build
- run: yarn workspaces foreach --all --include "@apollo/*" run test | tee $GITHUB_STEP_SUMMARY; exit ${PIPESTATUS[0]}
packageShapes:
name: Test bundles
name: Test Bundles
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
runs-on: ubuntu-latest
strategy:
Expand All @@ -49,20 +49,38 @@ jobs:
- run: yarn workspaces foreach --all --include "@apollo/*" add -D -P @apollo/client@${{ matrix.version }}
- run: yarn workspaces foreach --all --include "@apollo/*" run test-bundle | tee $GITHUB_STEP_SUMMARY; exit ${PIPESTATUS[0]}
tests_playwright:
name: Run Playwright tests
name: Integration Tests
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
version: ["latest", "next"]
defaults:
run:
working-directory: ./integration-test
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "20.x"
cache: "yarn"
- run: yarn install --immutable
- name: Install Packages (Root)
run: yarn install --immutable
working-directory: ./
- name: Set target version of Apollo Client (Root)
run: yarn workspaces foreach --all --include "@apollo/*" add -D -P @apollo/client@${{ matrix.version }}
working-directory: ./

- name: Install Packages (Integration Test)
run: |
sed -ire '/^ checksum/d' yarn.lock
yarn install
env:
YARN_ENABLE_IMMUTABLE_INSTALLS: "false"

- name: Set target version of Apollo Client (Integration Test)
run: yarn workspaces foreach --all --include "@integration-test/*" add @apollo/client@${{ matrix.version }}

- name: Get installed Playwright version
id: playwright-version
Expand All @@ -79,12 +97,6 @@ jobs:
- run: npx playwright install-deps
if: steps.playwright-cache.outputs.cache-hit == 'true'

- run: yarn workspaces foreach --all --include "@apollo/*" add -D -P @apollo/client@${{ matrix.version }}
- run: yarn workspaces foreach --all --include "@integration-test/*" add @apollo/client@${{ matrix.version }}

- name: Build libraries
run: yarn workspaces foreach --all -t --include "@apollo/*" run build

- name: "Next.js: Build"
run: yarn workspace @integration-test/nextjs run build
- name: "Next.js: Test"
Expand All @@ -99,3 +111,6 @@ jobs:
run: yarn workspace @integration-test/vite-streaming run build
- name: "Vite Streaming: Test"
run: yarn workspace @integration-test/vite-streaming run test | tee $GITHUB_STEP_SUMMARY; exit ${PIPESTATUS[0]}

- name: "Jest: Test"
run: yarn workspace @integration-test/jest run test | tee $GITHUB_STEP_SUMMARY; exit ${PIPESTATUS[0]}
39 changes: 16 additions & 23 deletions integration-test/.gitignore
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
node_modules/
tsconfig.tsbuildinfo
dist/
.yalc
yalc.lock
*.tgz
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.vscode/
.vercel
.next/
test-results/
8 changes: 8 additions & 0 deletions integration-test/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
compressionLevel: mixed
enableGlobalCache: false
nodeLinker: node-modules
npmAuthToken: "${NODE_AUTH_TOKEN-}"
yarnPath: ../.yarn/releases/yarn-4.1.0.cjs
cacheFolder: "../.yarn/cache"
installStatePath: "./.yarn/integration-test-install-state.gz"
enableInlineBuilds: true
6 changes: 6 additions & 0 deletions integration-test/jest/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
presets: [
["@babel/preset-env", { targets: { node: "current" } }],
["@babel/preset-react", { runtime: "automatic" }],
],
};
7 changes: 7 additions & 0 deletions integration-test/jest/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @type {import('jest').Config} */
const config = {
testEnvironment: "jsdom",
transformIgnorePatterns: [],
};

module.exports = config;
25 changes: 25 additions & 0 deletions integration-test/jest/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@integration-test/jest",
"scripts": {
"prepare": "rm -rf node_modules/@apollo/client-react-streaming node_modules/@apollo/experimental-nextjs-app-support; mkdir -p node_modules/@apollo/client-react-streaming node_modules/@apollo/experimental-nextjs-app-support && cp -r ../../packages/client-react-streaming/{package.json,dist} node_modules/@apollo/client-react-streaming && cp -r ../../packages/experimental-nextjs-app-support/{package.json,dist} node_modules/@apollo/experimental-nextjs-app-support",
"test": "yarn prepare; jest"
},
"dependencies": {
"@apollo/client": "^3.9.5",
"@apollo/client-react-streaming": "workspace:*",
"@apollo/experimental-nextjs-app-support": "workspace:*",
"@graphql-tools/schema": "^10.0.3",
"graphql-tag": "^2.12.6"
},
"devDependencies": {
"@babel/core": "^7.24.0",
"@babel/preset-env": "^7.24.0",
"@babel/preset-react": "^7.23.3",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.1",
"@testing-library/user-event": "^14.5.2",
"babel-jest": "^29.7.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0"
}
}
81 changes: 81 additions & 0 deletions integration-test/jest/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Suspense, useState } from "react";
import {
ApolloNextAppProvider,
NextSSRApolloClient,
NextSSRInMemoryCache,
useSuspenseQuery,
} from "@apollo/experimental-nextjs-app-support/ssr";
import { SchemaLink } from "@apollo/client/link/schema/index.js";
import { gql, ApolloLink, Observable } from "@apollo/client/index.js";
import { schema } from "./schema";

const delayLink = new ApolloLink((operation, forward) => {
return new Observable((observer) => {
const handle = setTimeout(() => {
forward(operation).subscribe(observer);
}, 20);

return () => {
clearTimeout(handle);
};
});
});

export const makeClient = () => {
return new NextSSRApolloClient({
cache: new NextSSRInMemoryCache(),
link: delayLink.concat(new SchemaLink({ schema })),
});
};

function App() {
return (
<>
<h1>Jest integration test</h1>
<div className="card">
<ApolloNextAppProvider makeClient={makeClient}>
<Suspense fallback={<div>Loading...</div>}>
<Countries />
<Counter />
</Suspense>
</ApolloNextAppProvider>
</div>
</>
);
}

export const QUERY = gql`
query {
products {
id
title
}
}
`;

function Countries() {
const { data } = useSuspenseQuery(QUERY);

return (
<ul>
{data.products.map((product) => (
<li key={product.id}>{product.title}</li>
))}
</ul>
);
}

/**
* Counter components to test that the client has hydrated and is interactive.
*/
function Counter() {
const [counter, setCounter] = useState(0);
return (
<>
<div data-testid="counter">{counter}</div>
<button onClick={() => setCounter((x) => x + 1)}>increment</button>
</>
);
}

export default App;
29 changes: 29 additions & 0 deletions integration-test/jest/src/App.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";

import App from "./App";
import { resetNextSSRApolloSingletons } from "@apollo/experimental-nextjs-app-support/ssr";

afterEach(resetNextSSRApolloSingletons);

test("loads data", async () => {
render(<App />);

expect(screen.getByText("Loading...")).toBeInTheDocument();
expect(
await screen.findByText("Soft Warm Apollo Beanie")
).toBeInTheDocument();
expect(screen.queryByText("Loading...")).not.toBeInTheDocument();
});

test("is interactive", async () => {
render(<App />);

const counter = await screen.findByTestId("counter");
expect(counter.textContent).toBe("0");

await userEvent.click(screen.getByText("increment"));

expect(counter.textContent).toBe("1");
});
66 changes: 66 additions & 0 deletions integration-test/jest/src/hooks.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";

import { makeClient, QUERY } from "./App";
import {
ApolloNextAppProvider,
NextSSRApolloClient,
useQuery,
resetNextSSRApolloSingletons,
} from "@apollo/experimental-nextjs-app-support/ssr";
import { Suspense } from "react";

const wrapper = ({ children }) => (
<ApolloNextAppProvider makeClient={makeClient}>
<Suspense fallback={<div>Loading...</div>}>{children}</Suspense>
</ApolloNextAppProvider>
);

afterEach(resetNextSSRApolloSingletons);

/**
* We test that jest is using the "browser" build.
* This is important because the "browser" build will not try to transport
* data from the server to the browser.
*/
test("uses the browser build", () => {
expect(NextSSRApolloClient.name).toBe("ApolloClientBrowserImpl");
});

/**
* The SSR build would just skip all `useQuery` calls because their result
* would never be able to be transported to the browser anyways.
* In a SSR build, it would always render loading.
*/
test("`useQuery` renders", async () => {
const Component = () => {
const { data, loading } = useQuery(QUERY);
return loading ? (
<div>Loading...</div>
) : (
<div>{data.products[0].title}</div>
);
};
render(<Component />, { wrapper });

expect(screen.getByText("Loading...")).toBeInTheDocument();
expect(
await screen.findByText("Soft Warm Apollo Beanie")
).toBeInTheDocument();
expect(screen.queryByText("Loading...")).not.toBeInTheDocument();
});

test("will set up the data transport", () => {
render(<></>, { wrapper });
expect(globalThis[Symbol.for("ApolloSSRDataTransport")]).toBeDefined();
expect(globalThis[Symbol.for("ApolloClientSingleton")]).toBeDefined();
});

test("resetNextSSRApolloSingletons tears down global singletons", () => {
render(<></>, { wrapper });
// wrappers are now set up, see last test
// usually, we do this in `afterEach`
resetNextSSRApolloSingletons();
expect(globalThis[Symbol.for("ApolloSSRDataTransport")]).not.toBeDefined();
expect(globalThis[Symbol.for("ApolloClientSingleton")]).not.toBeDefined();
});
48 changes: 48 additions & 0 deletions integration-test/jest/src/schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { makeExecutableSchema } from "@graphql-tools/schema";
import gql from "graphql-tag";

const typeDefs = gql`
type Product {
id: String!
title: String!
}
type Query {
products: [Product!]!
}
`;

const resolvers = {
Query: {
products: async () => [
{
id: "product:5",
title: "Soft Warm Apollo Beanie",
},
{
id: "product:2",
title: "Stainless Steel Water Bottle",
},
{
id: "product:3",
title: "Athletic Baseball Cap",
},
{
id: "product:4",
title: "Baby Onesies",
},
{
id: "product:1",
title: "The Apollo T-Shirt",
},
{
id: "product:6",
title: "The Apollo Socks",
},
],
},
};

export const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
Loading

0 comments on commit 31cc402

Please sign in to comment.