├── integration
├── using-app
│ ├── next-env.d.ts
│ ├── pages
│ │ ├── hooks.tsx
│ │ ├── index.tsx
│ │ ├── router.tsx
│ │ └── _app.tsx
│ ├── tsconfig.json
│ ├── __snapshots__
│ │ └── index.test.ts.snap
│ └── index.test.ts
├── using-error
│ ├── next-env.d.ts
│ ├── pages
│ │ ├── invalid.tsx
│ │ ├── missing.tsx
│ │ ├── unhandled.tsx
│ │ └── _app.tsx
│ ├── tsconfig.json
│ └── index.test.ts
├── using-pages
│ ├── next-env.d.ts
│ ├── pages
│ │ ├── hooks.tsx
│ │ ├── index.tsx
│ │ ├── hooks-ssr.tsx
│ │ ├── ssr.tsx
│ │ └── router.tsx
│ ├── utils
│ │ └── with-apollo.tsx
│ ├── tsconfig.json
│ ├── __snapshots__
│ │ └── index.test.ts.snap
│ └── index.test.ts
├── using-app-class
│ ├── next-env.d.ts
│ ├── pages
│ │ ├── hooks.tsx
│ │ ├── index.tsx
│ │ ├── router.tsx
│ │ └── _app.tsx
│ ├── tsconfig.json
│ ├── __snapshots__
│ │ └── index.test.ts.snap
│ └── index.test.ts
├── using-app-no-ssr
│ ├── next-env.d.ts
│ ├── pages
│ │ ├── hooks.tsx
│ │ ├── index.tsx
│ │ ├── router.tsx
│ │ └── _app.tsx
│ ├── tsconfig.json
│ └── index.test.ts
└── next-test-utils.ts
├── src
├── index.ts
├── apollo.ts
├── types.ts
└── withApollo.tsx
├── .editorconfig
├── .gitignore
├── .github
└── workflows
│ └── nodejs.yml
├── tsconfig.json
├── tslint.json
├── LICENSE
├── package.json
├── README_v1.md
└── README.md
/integration/using-app/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/integration/using-error/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/integration/using-pages/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/integration/using-app-class/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/integration/using-app-no-ssr/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import withApollo from './withApollo';
2 |
3 | export * from './types';
4 | export { default as initApollo } from './apollo';
5 | export { withApollo };
6 | export default withApollo;
7 |
--------------------------------------------------------------------------------
/integration/using-error/pages/invalid.tsx:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 | import { Query } from 'react-apollo';
3 |
4 | const QUERY = gql`
5 | {
6 | hire {
7 | id
8 | name
9 | }
10 | }
11 | `;
12 |
13 | const Index = () => (
14 |
15 | {() => {
16 | throw new Error('invalid');
17 | }}
18 |
19 | );
20 |
21 | export default Index;
22 |
--------------------------------------------------------------------------------
/integration/using-error/pages/missing.tsx:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 | import { Query } from 'react-apollo';
3 |
4 | const QUERY = gql`
5 | {
6 | hire {
7 | id
8 | name
9 | }
10 | }
11 | `;
12 |
13 | const Index = () => (
14 |
15 | {() => {
16 | throw new Error('missing');
17 | }}
18 |
19 | );
20 |
21 | export default Index;
22 |
--------------------------------------------------------------------------------
/integration/using-error/pages/unhandled.tsx:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 | import { Query } from 'react-apollo';
3 |
4 | const QUERY = gql`
5 | {
6 | hire {
7 | id
8 | name
9 | }
10 | }
11 | `;
12 |
13 | const Index = () => (
14 |
15 | {() => {
16 | throw new Error('unhandled');
17 | }}
18 |
19 | );
20 |
21 | export default Index;
22 |
--------------------------------------------------------------------------------
/integration/using-app/pages/hooks.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from '@apollo/react-hooks';
2 | import gql from 'graphql-tag';
3 |
4 | const QUERY = gql`
5 | {
6 | hire {
7 | id
8 | name
9 | }
10 | }
11 | `;
12 |
13 | const Index = () => {
14 | const { loading, data } = useQuery(QUERY);
15 |
16 | if (loading || !data) {
17 | return
loading
;
18 | }
19 |
20 | return {data.hire.name}
;
21 | };
22 |
23 | export default Index;
24 |
--------------------------------------------------------------------------------
/integration/using-app-class/pages/hooks.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from '@apollo/react-hooks';
2 | import gql from 'graphql-tag';
3 |
4 | const QUERY = gql`
5 | {
6 | hire {
7 | id
8 | name
9 | }
10 | }
11 | `;
12 |
13 | const Index = () => {
14 | const { loading, data } = useQuery(QUERY);
15 |
16 | if (loading || !data) {
17 | return loading
;
18 | }
19 |
20 | return {data.hire.name}
;
21 | };
22 |
23 | export default Index;
24 |
--------------------------------------------------------------------------------
/integration/using-app-no-ssr/pages/hooks.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from '@apollo/react-hooks';
2 | import gql from 'graphql-tag';
3 |
4 | const QUERY = gql`
5 | {
6 | hire {
7 | id
8 | name
9 | }
10 | }
11 | `;
12 |
13 | const Index = () => {
14 | const { loading, data } = useQuery(QUERY);
15 |
16 | if (loading || !data) {
17 | return loading
;
18 | }
19 |
20 | return {data.hire.name}
;
21 | };
22 |
23 | export default Index;
24 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | # Change these settings to your own preference
10 | indent_style = space
11 | indent_size = 2
12 |
13 | # We recommend you to keep these unchanged
14 | end_of_line = lf
15 | charset = utf-8
16 | trim_trailing_whitespace = true
17 | insert_final_newline = true
18 |
19 | [*.md]
20 | trim_trailing_whitespace = false
21 |
--------------------------------------------------------------------------------
/integration/using-app/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 | import { Query } from 'react-apollo';
3 |
4 | const QUERY = gql`
5 | {
6 | hire {
7 | id
8 | name
9 | }
10 | }
11 | `;
12 |
13 | const Index = () => (
14 |
15 | {({ loading, data }: any) => {
16 | if (loading || !data) {
17 | return loading
;
18 | }
19 |
20 | return {data.hire.name}
;
21 | }}
22 |
23 | );
24 |
25 | export default Index;
26 |
--------------------------------------------------------------------------------
/integration/using-app-class/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 | import { Query } from 'react-apollo';
3 |
4 | const QUERY = gql`
5 | {
6 | hire {
7 | id
8 | name
9 | }
10 | }
11 | `;
12 |
13 | const Index = () => (
14 |
15 | {({ loading, data }: any) => {
16 | if (loading || !data) {
17 | return loading
;
18 | }
19 |
20 | return {data.hire.name}
;
21 | }}
22 |
23 | );
24 |
25 | export default Index;
26 |
--------------------------------------------------------------------------------
/integration/using-app-no-ssr/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 | import { Query } from 'react-apollo';
3 |
4 | const QUERY = gql`
5 | {
6 | hire {
7 | id
8 | name
9 | }
10 | }
11 | `;
12 |
13 | const Index = () => (
14 |
15 | {({ loading, data }: any) => {
16 | if (loading || !data) {
17 | return loading
;
18 | }
19 |
20 | return {data.hire.name}
;
21 | }}
22 |
23 | );
24 |
25 | export default Index;
26 |
--------------------------------------------------------------------------------
/integration/using-pages/pages/hooks.tsx:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 | import { useQuery } from '@apollo/react-hooks';
3 | import withApollo from '../utils/with-apollo';
4 |
5 | const QUERY = gql`
6 | {
7 | hire {
8 | id
9 | name
10 | }
11 | }
12 | `;
13 |
14 | const Index = () => {
15 | const { loading, data } = useQuery(QUERY);
16 |
17 | if (loading || !data) {
18 | return loading
;
19 | }
20 |
21 | return {data.hire.name}
;
22 | };
23 |
24 | export default withApollo(Index);
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Test fixtures
2 | .next
3 | out
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 |
12 | # Coverage directory used by tools like istanbul
13 | coverage
14 |
15 | # Dependency directories
16 | node_modules/
17 |
18 | # Compiled files
19 | lib
20 | dist
21 |
22 | # Yarn
23 | .yarn-integrity
24 |
25 | # Npm
26 | .npm
27 |
28 | # Optional eslint cache
29 | .eslintcache
30 |
31 | # Output of 'npm pack'
32 | *.tgz
33 |
34 | # dotenv environment variables file
35 | .env
36 |
37 |
--------------------------------------------------------------------------------
/integration/using-app/pages/router.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from '@apollo/react-hooks';
2 | import gql from 'graphql-tag';
3 | import { useRouter } from 'next/router';
4 |
5 | const QUERY = gql`
6 | {
7 | hire {
8 | id
9 | name
10 | }
11 | }
12 | `;
13 |
14 | let e = false;
15 |
16 | const Index = () => {
17 | const router = useRouter();
18 | useQuery(QUERY);
19 |
20 | if (!router || e) {
21 | e = true;
22 | return null router
;
23 | }
24 |
25 | return all good
;
26 | };
27 |
28 | export default Index;
29 |
--------------------------------------------------------------------------------
/integration/using-app-class/pages/router.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from '@apollo/react-hooks';
2 | import gql from 'graphql-tag';
3 | import { useRouter } from 'next/router';
4 |
5 | const QUERY = gql`
6 | {
7 | hire {
8 | id
9 | name
10 | }
11 | }
12 | `;
13 |
14 | let e = false;
15 |
16 | const Index = () => {
17 | const router = useRouter();
18 | useQuery(QUERY);
19 |
20 | if (!router || e) {
21 | e = true;
22 | return null router
;
23 | }
24 |
25 | return all good
;
26 | };
27 |
28 | export default Index;
29 |
--------------------------------------------------------------------------------
/integration/using-app-no-ssr/pages/router.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from '@apollo/react-hooks';
2 | import gql from 'graphql-tag';
3 | import { useRouter } from 'next/router';
4 |
5 | const QUERY = gql`
6 | {
7 | hire {
8 | id
9 | name
10 | }
11 | }
12 | `;
13 |
14 | let e = false;
15 |
16 | const Index = () => {
17 | const router = useRouter();
18 | useQuery(QUERY);
19 |
20 | if (!router || e) {
21 | e = true;
22 | return null router
;
23 | }
24 |
25 | return all good
;
26 | };
27 |
28 | export default Index;
29 |
--------------------------------------------------------------------------------
/integration/using-pages/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 | import { Query } from 'react-apollo';
3 | import withApollo from '../utils/with-apollo';
4 |
5 | const QUERY = gql`
6 | {
7 | hire {
8 | id
9 | name
10 | }
11 | }
12 | `;
13 |
14 | const Index = () => (
15 |
16 | {({ loading, data }: any) => {
17 | if (loading || !data) {
18 | return loading
;
19 | }
20 |
21 | return {data.hire.name}
;
22 | }}
23 |
24 | );
25 |
26 | export default withApollo(Index);
27 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: Node CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | strategy:
11 | matrix:
12 | node-version: [8.x, 10.x, 12.x]
13 |
14 | steps:
15 | - uses: actions/checkout@v1
16 | - name: Use Node.js ${{ matrix.node-version }}
17 | uses: actions/setup-node@v1
18 | with:
19 | node-version: ${{ matrix.node-version }}
20 | - name: yarn install, build, and test
21 | run: |
22 | yarn install
23 | yarn test
24 | env:
25 | CI: true
26 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "baseUrl": "./",
6 | "rootDir": "src",
7 | "outDir": "lib",
8 | "moduleResolution": "node",
9 | "jsx": "react",
10 | "strict": true,
11 | "noUnusedLocals": true,
12 | "noUnusedParameters": true,
13 | "noImplicitAny": true,
14 | "esModuleInterop": true,
15 | "declaration": true,
16 | "pretty": true,
17 | "typeRoots": ["./node_modules/@types"],
18 | "lib": ["dom"]
19 | },
20 | "exclude": ["node_modules", "integration", "lib"]
21 | }
22 |
--------------------------------------------------------------------------------
/integration/using-app-no-ssr/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { ApolloProvider } from '@apollo/react-hooks';
2 | import ApolloClient, { InMemoryCache } from 'apollo-boost';
3 | import withApollo from '../../../lib';
4 |
5 | const App = ({ Component, pageProps, apollo }) => (
6 |
7 |
8 |
9 | );
10 |
11 | export default withApollo(
12 | ({ initialState }) =>
13 | new ApolloClient({
14 | uri: 'http://mocked.com/graphql',
15 | cache: new InMemoryCache().restore(initialState || {})
16 | })
17 | )(App);
18 |
--------------------------------------------------------------------------------
/integration/using-pages/pages/hooks-ssr.tsx:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 | import { useQuery } from '@apollo/react-hooks';
3 | import { getDataFromTree } from '@apollo/react-ssr';
4 | import withApollo from '../utils/with-apollo';
5 |
6 | const QUERY = gql`
7 | {
8 | hire {
9 | id
10 | name
11 | }
12 | }
13 | `;
14 |
15 | const Index = () => {
16 | const { loading, data } = useQuery(QUERY);
17 |
18 | if (loading || !data) {
19 | return loading
;
20 | }
21 |
22 | return {data.hire.name}
;
23 | };
24 |
25 | export default withApollo(Index, { getDataFromTree });
26 |
--------------------------------------------------------------------------------
/integration/using-pages/pages/ssr.tsx:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 | import { Query, getDataFromTree } from 'react-apollo';
3 | import withApollo from '../utils/with-apollo';
4 |
5 | const QUERY = gql`
6 | {
7 | hire {
8 | id
9 | name
10 | }
11 | }
12 | `;
13 |
14 | const Index = () => (
15 |
16 | {({ loading, data }: any) => {
17 | if (loading || !data) {
18 | return loading
;
19 | }
20 |
21 | return {data.hire.name}
;
22 | }}
23 |
24 | );
25 |
26 | export default withApollo(Index, { getDataFromTree });
27 |
--------------------------------------------------------------------------------
/integration/using-pages/pages/router.tsx:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 | import { useQuery } from '@apollo/react-hooks';
3 | import { useRouter } from 'next/router';
4 | import withApollo from '../utils/with-apollo';
5 |
6 | const QUERY = gql`
7 | {
8 | hire {
9 | id
10 | name
11 | }
12 | }
13 | `;
14 |
15 | let e = false;
16 |
17 | const Index = () => {
18 | const router = useRouter();
19 | useQuery(QUERY);
20 |
21 | if (!router || e) {
22 | e = true;
23 | return null router
;
24 | }
25 |
26 | return all good
;
27 | };
28 |
29 | export default withApollo(Index);
30 |
--------------------------------------------------------------------------------
/integration/using-pages/utils/with-apollo.tsx:
--------------------------------------------------------------------------------
1 | import { ApolloProvider } from '@apollo/react-hooks';
2 | import ApolloClient, { InMemoryCache } from 'apollo-boost';
3 | import withApollo from '../../../lib';
4 |
5 | export default withApollo(
6 | ({ initialState }) =>
7 | new ApolloClient({
8 | uri: 'http://mocked.com/graphql',
9 | cache: new InMemoryCache().restore(initialState || {})
10 | }),
11 | {
12 | render: ({ Page, props }) => {
13 | return (
14 |
15 |
16 |
17 | );
18 | }
19 | }
20 | );
21 |
--------------------------------------------------------------------------------
/integration/using-app/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { ApolloProvider } from '@apollo/react-hooks';
2 | import { getDataFromTree } from '@apollo/react-ssr';
3 | import ApolloClient, { InMemoryCache } from 'apollo-boost';
4 | import withApollo from '../../../lib';
5 |
6 | const App = ({ Component, pageProps, apollo }) => (
7 |
8 |
9 |
10 | );
11 |
12 | export default withApollo(
13 | ({ initialState }) =>
14 | new ApolloClient({
15 | uri: 'http://mocked.com/graphql',
16 | cache: new InMemoryCache().restore(initialState || {})
17 | }),
18 | { getDataFromTree }
19 | )(App);
20 |
--------------------------------------------------------------------------------
/integration/using-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | // next.js defaults
2 | {
3 | "compilerOptions": {
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "strict": false,
13 | "forceConsistentCasingInFileNames": true,
14 | "noEmit": true,
15 | "esModuleInterop": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "jsx": "preserve"
21 | },
22 | "exclude": [
23 | "node_modules"
24 | ],
25 | "include": [
26 | "next-env.d.ts",
27 | "**/*.ts",
28 | "**/*.tsx"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/integration/using-error/tsconfig.json:
--------------------------------------------------------------------------------
1 | // next.js defaults
2 | {
3 | "compilerOptions": {
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "strict": false,
13 | "forceConsistentCasingInFileNames": true,
14 | "noEmit": true,
15 | "esModuleInterop": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "jsx": "preserve"
21 | },
22 | "exclude": [
23 | "node_modules"
24 | ],
25 | "include": [
26 | "next-env.d.ts",
27 | "**/*.ts",
28 | "**/*.tsx"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/integration/using-pages/tsconfig.json:
--------------------------------------------------------------------------------
1 | // next.js defaults
2 | {
3 | "compilerOptions": {
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "strict": false,
13 | "forceConsistentCasingInFileNames": true,
14 | "noEmit": true,
15 | "esModuleInterop": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "jsx": "preserve"
21 | },
22 | "exclude": [
23 | "node_modules"
24 | ],
25 | "include": [
26 | "next-env.d.ts",
27 | "**/*.ts",
28 | "**/*.tsx"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/integration/using-app-class/tsconfig.json:
--------------------------------------------------------------------------------
1 | // next.js defaults
2 | {
3 | "compilerOptions": {
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "strict": false,
13 | "forceConsistentCasingInFileNames": true,
14 | "noEmit": true,
15 | "esModuleInterop": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "jsx": "preserve"
21 | },
22 | "exclude": [
23 | "node_modules"
24 | ],
25 | "include": [
26 | "next-env.d.ts",
27 | "**/*.ts",
28 | "**/*.tsx"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/integration/using-app-no-ssr/tsconfig.json:
--------------------------------------------------------------------------------
1 | // next.js defaults
2 | {
3 | "compilerOptions": {
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "strict": false,
13 | "forceConsistentCasingInFileNames": true,
14 | "noEmit": true,
15 | "esModuleInterop": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "jsx": "preserve"
21 | },
22 | "exclude": [
23 | "node_modules"
24 | ],
25 | "include": [
26 | "next-env.d.ts",
27 | "**/*.ts",
28 | "**/*.tsx"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/integration/using-app-class/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { ApolloProvider } from '@apollo/react-hooks';
2 | import { getDataFromTree } from '@apollo/react-ssr';
3 | import ApolloClient, { InMemoryCache } from 'apollo-boost';
4 | import App from 'next/app';
5 | import withApollo from '../../../lib';
6 |
7 | class MyApp extends App {
8 | public render() {
9 | const { Component, pageProps, apollo } = this.props;
10 |
11 | return (
12 |
13 |
14 |
15 | );
16 | }
17 | }
18 |
19 | export default withApollo(
20 | ({ initialState }) =>
21 | new ApolloClient({
22 | uri: 'http://mocked.com/graphql',
23 | cache: new InMemoryCache().restore(initialState || {})
24 | }),
25 | { getDataFromTree }
26 | )(MyApp);
27 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint:latest",
5 | "tslint-eslint-rules",
6 | "tslint-config-prettier"
7 | ],
8 | "rulesDirectory": [
9 | "tslint-plugin-prettier"
10 | ],
11 | "rules": {
12 | "prettier": [
13 | true,
14 | {
15 | "singleQuote": true
16 | }
17 | ],
18 | "curly": [
19 | true,
20 | "ignore-same-line"
21 | ],
22 | "variable-name": [
23 | true,
24 | "ban-keywords",
25 | "check-format",
26 | "allow-leading-underscore",
27 | "allow-pascal-case"
28 | ],
29 | "interface-name": [
30 | true,
31 | "never-prefix"
32 | ],
33 | "array-type": false,
34 | "object-literal-sort-keys": false,
35 | "no-implicit-dependencies": false,
36 | "no-this-assignment": false,
37 | "max-classes-per-file": false,
38 | "no-submodule-imports": false,
39 | "prefer-object-spread": false
40 | },
41 | "jsRules": {}
42 | }
43 |
--------------------------------------------------------------------------------
/integration/using-error/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { ApolloProvider } from '@apollo/react-hooks';
2 | import { getDataFromTree } from '@apollo/react-ssr';
3 | import ApolloClient, { InMemoryCache } from 'apollo-boost';
4 | import withApollo from '../../../lib';
5 |
6 | const App = ({ Component, pageProps, apollo }) => (
7 |
8 |
9 |
10 | );
11 |
12 | export default withApollo(
13 | ({ initialState }) =>
14 | new ApolloClient({
15 | uri: 'http://mocked.com/graphql',
16 | cache: new InMemoryCache().restore(initialState || {})
17 | }),
18 | {
19 | getDataFromTree,
20 | onError: (error, ctx) => {
21 | if (error.message === 'missing') {
22 | ctx.res.statusCode = 404;
23 | ctx.res.end('Not Found');
24 | } else if (error.message === 'invalid') {
25 | ctx.res.statusCode = 500;
26 | ctx.res.end('Internal Server Error');
27 | } else {
28 | throw error;
29 | }
30 | }
31 | }
32 | )(App);
33 |
--------------------------------------------------------------------------------
/integration/using-app/__snapshots__/index.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Using _app @apollo/react-hooks support loads useQuery data on the server 1`] = `
4 | Object {
5 | "data": Object {
6 | "ROOT_QUERY": Object {
7 | "hire": Object {
8 | "generated": false,
9 | "id": "User:uniqueid",
10 | "type": "id",
11 | "typename": "User",
12 | },
13 | },
14 | "User:uniqueid": Object {
15 | "__typename": "User",
16 | "id": "uniqueid",
17 | "name": "Next Apollo",
18 | },
19 | },
20 | }
21 | `;
22 |
23 | exports[`Using _app react-apollo support loads data on the server 1`] = `
24 | Object {
25 | "data": Object {
26 | "ROOT_QUERY": Object {
27 | "hire": Object {
28 | "generated": false,
29 | "id": "User:uniqueid",
30 | "type": "id",
31 | "typename": "User",
32 | },
33 | },
34 | "User:uniqueid": Object {
35 | "__typename": "User",
36 | "id": "uniqueid",
37 | "name": "Next Apollo",
38 | },
39 | },
40 | }
41 | `;
42 |
--------------------------------------------------------------------------------
/src/apollo.ts:
--------------------------------------------------------------------------------
1 | import ApolloClient from 'apollo-client';
2 | // Polyfill fetch
3 | import 'isomorphic-unfetch';
4 | import { InitApolloClient, InitApolloOptions } from './types';
5 |
6 | let _apolloClient: ApolloClient;
7 |
8 | export default function initApollo(
9 | clientFn: InitApolloClient,
10 | options?: InitApolloOptions
11 | ): ApolloClient {
12 | if (!clientFn) {
13 | throw new Error(
14 | '[withApollo] the first param is missing and is required to get the ApolloClient'
15 | );
16 | }
17 |
18 | if (typeof window === 'undefined') {
19 | return getClient(clientFn, options);
20 | }
21 | if (!_apolloClient) {
22 | _apolloClient = getClient(clientFn, options);
23 | }
24 |
25 | return _apolloClient;
26 | }
27 |
28 | function getClient(
29 | clientFn: InitApolloClient,
30 | options: InitApolloOptions = {}
31 | ) {
32 | if (typeof clientFn !== 'function') {
33 | throw new Error(
34 | '[withApollo] requires a function that returns an ApolloClient'
35 | );
36 | }
37 |
38 | return clientFn(options);
39 | }
40 |
--------------------------------------------------------------------------------
/integration/using-app-class/__snapshots__/index.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Using _app with a Class Component @apollo/react-hooks support loads useQuery data on the server 1`] = `
4 | Object {
5 | "data": Object {
6 | "ROOT_QUERY": Object {
7 | "hire": Object {
8 | "generated": false,
9 | "id": "User:uniqueid",
10 | "type": "id",
11 | "typename": "User",
12 | },
13 | },
14 | "User:uniqueid": Object {
15 | "__typename": "User",
16 | "id": "uniqueid",
17 | "name": "Next Apollo",
18 | },
19 | },
20 | }
21 | `;
22 |
23 | exports[`Using _app with a Class Component react-apollo support loads data on the server 1`] = `
24 | Object {
25 | "data": Object {
26 | "ROOT_QUERY": Object {
27 | "hire": Object {
28 | "generated": false,
29 | "id": "User:uniqueid",
30 | "type": "id",
31 | "typename": "User",
32 | },
33 | },
34 | "User:uniqueid": Object {
35 | "__typename": "User",
36 | "id": "uniqueid",
37 | "name": "Next Apollo",
38 | },
39 | },
40 | }
41 | `;
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Luis Fernando Alvarez D.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/integration/using-pages/__snapshots__/index.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Using pages @apollo/react-hooks support loads useQuery data on the server 1`] = `
4 | Object {
5 | "apollo": null,
6 | "apolloState": Object {
7 | "data": Object {
8 | "ROOT_QUERY": Object {
9 | "hire": Object {
10 | "generated": false,
11 | "id": "User:uniqueid",
12 | "type": "id",
13 | "typename": "User",
14 | },
15 | },
16 | "User:uniqueid": Object {
17 | "__typename": "User",
18 | "id": "uniqueid",
19 | "name": "Next Apollo",
20 | },
21 | },
22 | },
23 | }
24 | `;
25 |
26 | exports[`Using pages react-apollo support loads data on the server 1`] = `
27 | Object {
28 | "apollo": null,
29 | "apolloState": Object {
30 | "data": Object {
31 | "ROOT_QUERY": Object {
32 | "hire": Object {
33 | "generated": false,
34 | "id": "User:uniqueid",
35 | "type": "id",
36 | "typename": "User",
37 | },
38 | },
39 | "User:uniqueid": Object {
40 | "__typename": "User",
41 | "id": "uniqueid",
42 | "name": "Next Apollo",
43 | },
44 | },
45 | },
46 | }
47 | `;
48 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import ApolloClient from 'apollo-client';
2 | import { IncomingHttpHeaders } from 'http';
3 | import { NextPage, NextPageContext } from 'next';
4 | import { AppContext } from 'next/app';
5 | import { NextRouter } from 'next/dist/client/router';
6 | import { ReactNode } from 'react';
7 |
8 | export interface WithApolloOptions {
9 | getDataFromTree?: (
10 | tree: ReactNode,
11 | context?: { [key: string]: any }
12 | ) => Promise;
13 | render?: (props: { Page: NextPage; props: any }) => any;
14 | onError?: (error: Error, ctx?: NextPageContext) => void;
15 | }
16 |
17 | export interface WithApolloState {
18 | data?: TCache;
19 | }
20 |
21 | export interface WithApolloProps {
22 | apolloState: WithApolloState;
23 | apollo: ApolloClient;
24 | router: NextRouter;
25 | }
26 |
27 | export interface InitApolloOptions {
28 | ctx?: NextPageContext;
29 | headers?: IncomingHttpHeaders;
30 | router?: NextRouter;
31 | initialState?: TCache;
32 | }
33 |
34 | export type InitApolloClient = (
35 | options: InitApolloOptions
36 | ) => ApolloClient;
37 |
38 | export interface ApolloPageContext extends NextPageContext {
39 | // Custom prop added by withApollo
40 | apolloClient: ApolloClient;
41 | }
42 |
43 | export interface ApolloAppContext extends AppContext {
44 | ctx: ApolloPageContext;
45 | AppTree: any;
46 | }
47 |
48 | export type ApolloContext = ApolloPageContext | ApolloAppContext;
49 |
--------------------------------------------------------------------------------
/integration/using-error/index.test.ts:
--------------------------------------------------------------------------------
1 | import nock from 'nock';
2 | import {
3 | nextServer,
4 | nextBuild,
5 | nextExport,
6 | startApp,
7 | fetchViaHTTP
8 | } from '../next-test-utils';
9 |
10 | const appDir = __dirname;
11 | let appPort: number;
12 | let server: any;
13 | let app: any;
14 |
15 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 30;
16 |
17 | beforeAll(async () => {
18 | await nextBuild(appDir);
19 | app = nextServer({
20 | dir: appDir,
21 | dev: false,
22 | quiet: true
23 | });
24 | server = await startApp(app);
25 | appPort = server.address().port;
26 | });
27 |
28 | afterAll(() => server.close());
29 |
30 | beforeEach(() => {
31 | // Fake GraphQL server
32 | nock('http://mocked.com')
33 | .post(`/graphql`)
34 | .reply(200, {
35 | data: {
36 | hire: { __typename: 'User', id: 'uniqueid', name: 'Next Apollo' }
37 | }
38 | });
39 | });
40 |
41 | describe('Using onError', () => {
42 | it('breaks build from unhandled GraphQL errors', async () => {
43 | const { stderr } = await nextExport(appDir, [], { stderr: true });
44 | expect(stderr).toMatch('unhandled');
45 | });
46 |
47 | it('returns 500 via context', async () => {
48 | const response = await fetchViaHTTP(appPort, '/invalid');
49 | expect(response.status).toEqual(500);
50 | const text = await response.text();
51 | expect(text).toMatch('Internal Server Error');
52 | });
53 |
54 | it('returns 404 via context', async () => {
55 | const response = await fetchViaHTTP(appPort, '/missing');
56 | expect(response.status).toEqual(404);
57 | const text = await response.text();
58 | expect(text).toMatch('Not Found');
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/integration/using-app-no-ssr/index.test.ts:
--------------------------------------------------------------------------------
1 | import nock from 'nock';
2 | import {
3 | nextServer,
4 | nextBuild,
5 | startApp,
6 | renderViaHTTP
7 | } from '../next-test-utils';
8 |
9 | const appDir = __dirname;
10 | let appPort: number;
11 | let server: any;
12 | let app: any;
13 |
14 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 30;
15 |
16 | beforeAll(async () => {
17 | await nextBuild(appDir);
18 | app = nextServer({
19 | dir: appDir,
20 | dev: false,
21 | quiet: true
22 | });
23 | server = await startApp(app);
24 | appPort = server.address().port;
25 | });
26 |
27 | afterAll(() => server.close());
28 |
29 | beforeEach(() => {
30 | // Fake GraphQL server
31 | nock('http://mocked.com')
32 | .post(`/graphql`)
33 | .reply(200, {
34 | data: {
35 | hire: { __typename: 'User', id: 'uniqueid', name: 'Next Apollo' }
36 | }
37 | });
38 | });
39 |
40 | describe('Using _app without SSR', () => {
41 | describe('react-apollo support', () => {
42 | it('loads data on the client', async () => {
43 | const html = await renderViaHTTP(appPort, '/');
44 | expect(html).toContain('loading
');
45 | });
46 | });
47 |
48 | describe('@apollo/react-hooks support', () => {
49 | it('loads useQuery data on the client', async () => {
50 | const html = await renderViaHTTP(appPort, '/hooks');
51 | expect(html).toContain('loading
');
52 | });
53 | });
54 |
55 | describe('ssr smoke', () => {
56 | it('useRouter is never null', async () => {
57 | const html = await renderViaHTTP(appPort, '/router');
58 |
59 | if (!html.includes('all good
')) {
60 | throw new Error(`
61 | The built in next hook useRouter() returned null during a render.
62 | getDataFromTree should be called on AppTree no App so the Context
63 | is always provided.
64 | `);
65 | }
66 | });
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/integration/using-app/index.test.ts:
--------------------------------------------------------------------------------
1 | import nock from 'nock';
2 | import {
3 | nextServer,
4 | nextBuild,
5 | startApp,
6 | renderViaHTTP,
7 | extractNextData
8 | } from '../next-test-utils';
9 |
10 | const appDir = __dirname;
11 | let appPort: number;
12 | let server: any;
13 | let app: any;
14 |
15 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 30;
16 |
17 | beforeAll(async () => {
18 | await nextBuild(appDir);
19 | app = nextServer({
20 | dir: appDir,
21 | dev: false,
22 | quiet: true
23 | });
24 | server = await startApp(app);
25 | appPort = server.address().port;
26 | });
27 |
28 | afterAll(() => server.close());
29 |
30 | beforeEach(() => {
31 | // Fake GraphQL server
32 | nock('http://mocked.com')
33 | .post(`/graphql`)
34 | .reply(200, {
35 | data: {
36 | hire: { __typename: 'User', id: 'uniqueid', name: 'Next Apollo' }
37 | }
38 | });
39 | });
40 |
41 | describe('Using _app', () => {
42 | describe('react-apollo support', () => {
43 | it('loads data on the server', async () => {
44 | const html = await renderViaHTTP(appPort, '/');
45 | expect(html).toContain('Next Apollo
');
46 |
47 | const { apolloState } = extractNextData(html);
48 | expect(apolloState).toMatchSnapshot();
49 | });
50 | });
51 |
52 | describe('@apollo/react-hooks support', () => {
53 | it('loads useQuery data on the server', async () => {
54 | const html = await renderViaHTTP(appPort, '/hooks');
55 | expect(html).toContain('Next Apollo
');
56 |
57 | const { apolloState } = extractNextData(html);
58 | expect(apolloState).toMatchSnapshot();
59 | });
60 | });
61 |
62 | describe('ssr smoke', () => {
63 | it('useRouter is never null', async () => {
64 | const html = await renderViaHTTP(appPort, '/router');
65 |
66 | if (!html.includes('all good
')) {
67 | throw new Error(`
68 | The built in next hook useRouter() returned null during a render.
69 | getDataFromTree should be called on AppTree no App so the Context
70 | is always provided.
71 | `);
72 | }
73 | });
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/integration/using-app-class/index.test.ts:
--------------------------------------------------------------------------------
1 | import nock from 'nock';
2 | import {
3 | nextServer,
4 | nextBuild,
5 | startApp,
6 | renderViaHTTP,
7 | extractNextData
8 | } from '../next-test-utils';
9 |
10 | const appDir = __dirname;
11 | let appPort: number;
12 | let server: any;
13 | let app: any;
14 |
15 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 30;
16 |
17 | beforeAll(async () => {
18 | await nextBuild(appDir);
19 | app = nextServer({
20 | dir: appDir,
21 | dev: false,
22 | quiet: true
23 | });
24 | server = await startApp(app);
25 | appPort = server.address().port;
26 | });
27 |
28 | afterAll(() => server.close());
29 |
30 | beforeEach(() => {
31 | // Fake GraphQL server
32 | nock('http://mocked.com')
33 | .post(`/graphql`)
34 | .reply(200, {
35 | data: {
36 | hire: { __typename: 'User', id: 'uniqueid', name: 'Next Apollo' }
37 | }
38 | });
39 | });
40 |
41 | describe('Using _app with a Class Component', () => {
42 | describe('react-apollo support', () => {
43 | it('loads data on the server', async () => {
44 | const html = await renderViaHTTP(appPort, '/');
45 | expect(html).toContain('Next Apollo
');
46 |
47 | const { apolloState } = extractNextData(html);
48 | expect(apolloState).toMatchSnapshot();
49 | });
50 | });
51 |
52 | describe('@apollo/react-hooks support', () => {
53 | it('loads useQuery data on the server', async () => {
54 | const html = await renderViaHTTP(appPort, '/hooks');
55 | expect(html).toContain('Next Apollo
');
56 |
57 | const { apolloState } = extractNextData(html);
58 | expect(apolloState).toMatchSnapshot();
59 | });
60 | });
61 |
62 | describe('ssr smoke', () => {
63 | it('useRouter is never null', async () => {
64 | const html = await renderViaHTTP(appPort, '/router');
65 |
66 | if (!html.includes('all good
')) {
67 | throw new Error(`
68 | The built in next hook useRouter() returned null during a render.
69 | getDataFromTree should be called on AppTree no App so the Context
70 | is always provided.
71 | `);
72 | }
73 | });
74 | });
75 | })
76 |
77 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-with-apollo",
3 | "version": "5.3.0",
4 | "description": "Apollo HOC for Next.js",
5 | "main": "lib/index.js",
6 | "types": "lib/index.d.ts",
7 | "files": [
8 | "lib"
9 | ],
10 | "scripts": {
11 | "fix": "tslint -p tsconfig.json --fix",
12 | "tslint": "tslint -c tslint.json -p tsconfig.json -t codeFrame",
13 | "build": "tsc",
14 | "watch": "tsc -w",
15 | "test": "yarn tslint && yarn build && jest",
16 | "prepublishOnly": "yarn test"
17 | },
18 | "author": "lfades",
19 | "license": "MIT",
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/lfades/next-with-apollo.git"
23 | },
24 | "bugs": {
25 | "url": "https://github.com/lfades/next-with-apollo/issues"
26 | },
27 | "prettier": {
28 | "singleQuote": true
29 | },
30 | "jest": {
31 | "rootDir": "integration",
32 | "testMatch": [
33 | "**/*.test.[jt]s?(x)"
34 | ],
35 | "bail": true,
36 | "preset": "ts-jest/presets/js-with-babel",
37 | "testEnvironment": "node",
38 | "globals": {
39 | "ts-jest": {
40 | "isolatedModules": true
41 | }
42 | }
43 | },
44 | "dependencies": {
45 | "isomorphic-unfetch": "^3.0.0"
46 | },
47 | "peerDependencies": {
48 | "next": "^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0",
49 | "react": "^16.6.0 || ^17.0.0 || ^18.0.0",
50 | "react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0"
51 | },
52 | "devDependencies": {
53 | "@apollo/react-hooks": "^3.0.1",
54 | "@apollo/react-ssr": "^3.1.3",
55 | "@types/cross-spawn": "^6.0.1",
56 | "@types/jest": "^24.0.25",
57 | "@types/node": "^13.1.2",
58 | "@types/node-fetch": "^2.5.4",
59 | "@types/react": "^16.9.17",
60 | "@types/react-dom": "^16.9.4",
61 | "apollo-boost": "^0.4.7",
62 | "apollo-client": "^2.6.8",
63 | "cross-spawn": "^7.0.1",
64 | "get-port": "^5.1.0",
65 | "graphql": "^14.5.8",
66 | "jest": "^24.9.0",
67 | "next": "^9.1.6",
68 | "nock": "^11.7.0",
69 | "node-fetch": "2.6.1",
70 | "prettier": "^1.19.1",
71 | "react": "^16.12.0",
72 | "react-apollo": "^3.0.1",
73 | "react-dom": "^16.12.0",
74 | "ts-jest": "^24.2.0",
75 | "tslint": "^5.20.1",
76 | "tslint-config-prettier": "^1.18.0",
77 | "tslint-eslint-rules": "^5.4.0",
78 | "tslint-plugin-prettier": "^2.1.0",
79 | "typescript": "^3.7.4"
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/integration/using-pages/index.test.ts:
--------------------------------------------------------------------------------
1 | import nock from 'nock';
2 | import {
3 | nextServer,
4 | nextBuild,
5 | startApp,
6 | renderViaHTTP,
7 | extractNextData
8 | } from '../next-test-utils';
9 |
10 | const appDir = __dirname;
11 | let appPort: number;
12 | let server: any;
13 | let app: any;
14 |
15 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 30;
16 |
17 | beforeAll(async () => {
18 | await nextBuild(appDir);
19 | app = nextServer({
20 | dir: appDir,
21 | dev: false,
22 | quiet: true
23 | });
24 | server = await startApp(app);
25 | appPort = server.address().port;
26 | });
27 |
28 | afterAll(() => server.close());
29 |
30 | beforeEach(() => {
31 | // Fake GraphQL server
32 | nock('http://mocked.com')
33 | .post(`/graphql`)
34 | .reply(200, {
35 | data: {
36 | hire: { __typename: 'User', id: 'uniqueid', name: 'Next Apollo' }
37 | }
38 | });
39 | });
40 |
41 | describe('Using pages', () => {
42 | describe('react-apollo support', () => {
43 | it('loads data on the server', async () => {
44 | const html = await renderViaHTTP(appPort, '/ssr');
45 | expect(html).toContain('Next Apollo
');
46 |
47 | const { pageProps } = extractNextData(html);
48 | expect(pageProps).toMatchSnapshot();
49 | });
50 |
51 | it('loads loading state with SSR disabled', async () => {
52 | const html = await renderViaHTTP(appPort, '/');
53 | expect(html).toContain('loading
');
54 |
55 | const { pageProps } = extractNextData(html);
56 | expect(pageProps).toEqual({});
57 | });
58 | });
59 |
60 | describe('@apollo/react-hooks support', () => {
61 | it('loads useQuery data on the server', async () => {
62 | const html = await renderViaHTTP(appPort, '/hooks-ssr');
63 | expect(html).toContain('Next Apollo
');
64 |
65 | const { pageProps } = extractNextData(html);
66 | expect(pageProps).toMatchSnapshot();
67 | });
68 |
69 | it('loads useQuery loading state with SSR disabled', async () => {
70 | const html = await renderViaHTTP(appPort, '/hooks');
71 | expect(html).toContain('loading
');
72 |
73 | const { pageProps } = extractNextData(html);
74 | expect(pageProps).toEqual({});
75 | });
76 | });
77 |
78 | it('useRouter is never null', async () => {
79 | const html = await renderViaHTTP(appPort, '/router');
80 |
81 | if (!html.includes('all good
')) {
82 | throw new Error(`
83 | The built in next hook useRouter() returned null during a render.
84 | getDataFromTree should be called on AppTree no App so the Context
85 | is always provided.
86 | `);
87 | }
88 | });
89 | });
90 |
--------------------------------------------------------------------------------
/integration/next-test-utils.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import http from 'http';
3 | import spawn from 'cross-spawn';
4 | import nextServer from 'next';
5 | import fetch, { RequestInit } from 'node-fetch';
6 |
7 | /**
8 | * These utils are very similar to the ones used by Next.js in their tests
9 | */
10 |
11 | type FirstArgument = T extends (arg: infer U) => any ? U : any;
12 |
13 | type NextServerOptions = FirstArgument;
14 |
15 | type NextCommandOptions = {
16 | env?: any;
17 | stderr?: boolean;
18 | stdout?: boolean;
19 | };
20 |
21 | function promiseCall(obj: any, method: string, ...args: any) {
22 | return new Promise((resolve, reject) => {
23 | const newArgs = [
24 | ...args,
25 | function(err: Error, res: any) {
26 | if (err) return reject(err);
27 | resolve(res);
28 | }
29 | ];
30 |
31 | obj[method](...newArgs);
32 | });
33 | }
34 |
35 | export { nextServer };
36 |
37 | export async function startApp(options: NextServerOptions) {
38 | const app = nextServer(options);
39 |
40 | await app.prepare();
41 |
42 | const handler = app.getRequestHandler();
43 | const server = http.createServer(handler);
44 |
45 | (server as any).__app = app;
46 |
47 | await promiseCall(server, 'listen');
48 |
49 | return server;
50 | }
51 |
52 | export async function stopApp(server: http.Server) {
53 | const app = (server as any)._app;
54 |
55 | if (app) await app.close();
56 | await promiseCall(server, 'close');
57 | }
58 |
59 | export function runNextCommand(
60 | args: string[],
61 | options: NextCommandOptions = {}
62 | ) {
63 | const nextDir = path.dirname(require.resolve('next/package'));
64 | const nextBin = path.join(nextDir, 'dist/bin/next');
65 | const cwd = nextDir;
66 | const env = { ...process.env, ...options.env, NODE_ENV: '' };
67 |
68 | return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
69 | console.log(`Running command "next ${args.join(' ')}"`);
70 | const instance = spawn('node', [nextBin, ...args], {
71 | cwd,
72 | env,
73 | stdio: ['ignore', 'pipe', 'pipe']
74 | });
75 |
76 | let stderrOutput = '';
77 | if (options.stderr) {
78 | instance.stderr!.on('data', function(chunk) {
79 | stderrOutput += chunk;
80 | });
81 | }
82 |
83 | let stdoutOutput = '';
84 | if (options.stdout) {
85 | instance.stdout!.on('data', function(chunk) {
86 | stdoutOutput += chunk;
87 | });
88 | }
89 |
90 | instance.on('close', () => {
91 | resolve({
92 | stdout: stdoutOutput,
93 | stderr: stderrOutput
94 | });
95 | });
96 |
97 | instance.on('error', (err: any) => {
98 | err.stdout = stdoutOutput;
99 | err.stderr = stderrOutput;
100 | reject(err);
101 | });
102 | });
103 | }
104 |
105 | export function nextBuild(
106 | dir: string,
107 | args: string[] = [],
108 | opts?: NextCommandOptions
109 | ) {
110 | return runNextCommand(['build', dir, ...args], opts);
111 | }
112 |
113 | export function nextExport(
114 | dir: string,
115 | args: string[] = [],
116 | opts?: NextCommandOptions
117 | ) {
118 | return runNextCommand(['export', dir, ...args], opts);
119 | }
120 |
121 | export function fetchViaHTTP(
122 | appPort: number,
123 | pathname: string,
124 | opts?: RequestInit
125 | ) {
126 | const url = `http://localhost:${appPort}${pathname}`;
127 | return fetch(url, opts);
128 | }
129 |
130 | export function renderViaHTTP(appPort: number, pathname: string) {
131 | return fetchViaHTTP(appPort, pathname).then(res => res.text());
132 | }
133 |
134 | export function extractNextData(html: string) {
135 | const R = /