From ba3e7d9fa7d46e4c636148bbf01552833db0ceda Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Tue, 1 Aug 2023 10:19:18 +0200 Subject: [PATCH] Apply Prettier (#11111) * flip .prettierignore to list ignored files * apply formatting to currently not-ignored files * upgrade prettier to v3 * apply agreed-up change to `"singleQuote": false` * move prettier config into file * format integration-tests * format config folder * ignore snap files per default * format the whole non-ignored repo * format `react` folder tests * format `react` folder --- .attw.json | 5 +- .eslintrc | 13 +- .github/workflows/close-stale-issues.yml | 216 +- .github/workflows/lock.yml | 12 +- .prettierignore | 302 +- .prettierrc | 8 + .size-limit.cjs | 82 +- .vscode/launch.json | 12 +- .vscode/settings.json | 6 +- config/bundlesize.ts | 16 +- config/entryPoints.js | 70 +- config/helpers.ts | 20 +- config/jest.config.js | 28 +- config/postprocessDist.ts | 18 +- config/precheck.js | 13 +- config/prepareDist.js | 60 +- config/processInvariants.ts | 109 +- config/rewriteSourceMaps.ts | 64 +- config/rollup.config.js | 79 +- config/tsconfig.json | 4 +- config/version.js | 30 +- .../browser-esm/html/jsdeliver-esm.html | 80 +- .../browser-esm/html/jspm-prepared.html | 118 +- .../browser-esm/html/unpkg-unmangled.html | 112 +- .../browser-esm/playwright.config.ts | 4 +- .../tests/playwright/jsdeliver-esm.test.ts | 14 +- .../tests/playwright/jspm-prepared.test.ts | 14 +- .../tests/playwright/unpkg-unmangled.test.ts | 14 +- integration-tests/cra4/playwright.config.ts | 4 +- integration-tests/cra4/public/index.html | 37 +- integration-tests/cra4/src/App.tsx | 12 +- integration-tests/cra4/src/index.tsx | 8 +- .../tests/playwright/apollo-client.test.ts | 14 +- integration-tests/cra4/tsconfig.json | 10 +- integration-tests/cra5/playwright.config.ts | 4 +- integration-tests/cra5/public/index.html | 33 +- integration-tests/cra5/src/App.tsx | 12 +- integration-tests/cra5/src/index.tsx | 8 +- .../tests/playwright/apollo-client.test.ts | 14 +- integration-tests/cra5/tsconfig.json | 10 +- integration-tests/next/next.config.js | 4 +- integration-tests/next/playwright.config.ts | 4 +- .../next/src/app/cc/ApolloWrapper.tsx | 22 +- integration-tests/next/src/app/cc/layout.tsx | 2 +- integration-tests/next/src/app/cc/page.tsx | 8 +- integration-tests/next/src/app/client.ts | 6 +- integration-tests/next/src/app/layout.tsx | 4 +- integration-tests/next/src/app/page.tsx | 6 +- .../next/src/libs/apolloClient.ts | 30 +- integration-tests/next/src/libs/schemaLink.ts | 4 +- integration-tests/next/src/pages/_app.tsx | 6 +- .../next/src/pages/pages-no-ssr.tsx | 6 +- integration-tests/next/src/pages/pages.tsx | 12 +- .../tests/playwright/apollo-client.test.ts | 32 +- integration-tests/node-esm/test-cjs.cjs | 26 +- integration-tests/node-esm/test-esm.mjs | 26 +- integration-tests/node-standard/test-cjs.js | 26 +- integration-tests/node-standard/test-esm.mjs | 26 +- integration-tests/shared/fixture.ts | 22 +- integration-tests/shared/playwright.config.ts | 6 +- integration-tests/vite-swc/index.html | 22 +- .../vite-swc/playwright.config.ts | 4 +- integration-tests/vite-swc/src/App.tsx | 12 +- integration-tests/vite-swc/src/main.tsx | 8 +- .../tests/playwright/apollo-client.test.ts | 14 +- integration-tests/vite-swc/vite.config.ts | 6 +- integration-tests/vite/index.html | 22 +- integration-tests/vite/playwright.config.ts | 4 +- integration-tests/vite/src/App.tsx | 12 +- integration-tests/vite/src/main.tsx | 12 +- .../tests/playwright/apollo-client.test.ts | 14 +- integration-tests/vite/vite.config.ts | 6 +- package-lock.json | 42 +- package.json | 10 +- src/core/__tests__/equalByQuery.ts | 911 +-- src/core/equalByQuery.ts | 12 +- src/dev/index.ts | 6 +- src/dev/loadDevMessages.ts | 4 +- src/dev/loadErrorMessageHandler.ts | 10 +- src/dev/loadErrorMessages.ts | 4 +- .../__tests__/removeTypenameFromVariables.ts | 156 +- src/link/remove-typename/index.ts | 2 +- .../removeTypenameFromVariables.ts | 16 +- src/react/cache/QueryReference.ts | 52 +- src/react/cache/SuspenseCache.ts | 14 +- src/react/cache/getSuspenseCache.ts | 10 +- src/react/cache/index.ts | 20 +- src/react/cache/types.ts | 6 +- src/react/components/Mutation.tsx | 10 +- src/react/components/Query.tsx | 17 +- src/react/components/Subscription.tsx | 21 +- .../__tests__/client/Mutation.test.tsx | 751 ++- .../__tests__/client/Query.test.tsx | 608 +- .../__tests__/client/Subscription.test.tsx | 273 +- .../__tests__/ssr/getDataFromTree.test.tsx | 137 +- .../components/__tests__/ssr/server.test.tsx | 120 +- src/react/components/index.ts | 8 +- src/react/components/types.ts | 20 +- src/react/context/ApolloConsumer.tsx | 12 +- src/react/context/ApolloContext.ts | 32 +- src/react/context/ApolloProvider.tsx | 10 +- .../context/__tests__/ApolloConsumer.test.tsx | 32 +- .../context/__tests__/ApolloProvider.test.tsx | 133 +- src/react/context/index.ts | 10 +- .../hoc/__tests__/client-option.test.tsx | 192 +- src/react/hoc/__tests__/fragments.test.tsx | 135 +- .../hoc/__tests__/mutations/index.test.tsx | 226 +- .../__tests__/mutations/lifecycle.test.tsx | 146 +- .../hoc/__tests__/mutations/queries.test.tsx | 410 +- .../mutations/recycled-queries.test.tsx | 150 +- src/react/hoc/__tests__/queries/api.test.tsx | 314 +- .../hoc/__tests__/queries/errors.test.tsx | 793 +-- .../hoc/__tests__/queries/index.test.tsx | 627 +- .../hoc/__tests__/queries/lifecycle.test.tsx | 208 +- .../hoc/__tests__/queries/loading.test.tsx | 1073 +-- .../queries/observableQuery.test.tsx | 427 +- .../hoc/__tests__/queries/polling.test.tsx | 231 +- .../__tests__/queries/recomposeWithState.ts | 5 +- .../hoc/__tests__/queries/reducer.test.tsx | 281 +- src/react/hoc/__tests__/queries/skip.test.tsx | 1333 ++-- .../__tests__/queries/updateQuery.test.tsx | 404 +- .../hoc/__tests__/shared-operations.test.tsx | 102 +- .../__tests__/ssr/getDataFromTree.test.tsx | 512 +- src/react/hoc/__tests__/ssr/server.test.tsx | 170 +- src/react/hoc/__tests__/statics.test.tsx | 24 +- .../subscriptions/subscriptions.test.tsx | 132 +- src/react/hoc/graphql.tsx | 16 +- src/react/hoc/hoc-utils.tsx | 18 +- src/react/hoc/index.ts | 14 +- src/react/hoc/mutation-hoc.tsx | 83 +- src/react/hoc/query-hoc.tsx | 43 +- src/react/hoc/subscription-hoc.tsx | 48 +- src/react/hoc/types.ts | 30 +- src/react/hoc/withApollo.tsx | 24 +- .../hooks/__tests__/useApolloClient.test.tsx | 22 +- .../__tests__/useBackgroundQuery.test.tsx | 1100 ++-- .../hooks/__tests__/useFragment.test.tsx | 1082 ++-- .../hooks/__tests__/useLazyQuery.test.tsx | 1451 +++-- .../hooks/__tests__/useMutation.test.tsx | 2109 +++--- src/react/hooks/__tests__/useQuery.test.tsx | 5767 ++++++++++------- .../hooks/__tests__/useReactiveVar.test.tsx | 216 +- .../hooks/__tests__/useSubscription.test.tsx | 723 ++- .../hooks/__tests__/useSuspenseQuery.test.tsx | 2454 +++---- src/react/hooks/index.ts | 22 +- .../internal/__tests__/useDeepMemo.test.ts | 16 +- src/react/hooks/internal/__use.ts | 12 +- src/react/hooks/internal/index.ts | 6 +- src/react/hooks/internal/useDeepMemo.ts | 6 +- .../internal/useIsomorphicLayoutEffect.ts | 4 +- src/react/hooks/useApolloClient.ts | 14 +- src/react/hooks/useBackgroundQuery.ts | 60 +- src/react/hooks/useFragment.ts | 47 +- src/react/hooks/useLazyQuery.ts | 100 +- src/react/hooks/useMutation.ts | 219 +- src/react/hooks/useQuery.ts | 395 +- src/react/hooks/useReactiveVar.ts | 4 +- src/react/hooks/useReadQuery.ts | 13 +- src/react/hooks/useSubscription.ts | 60 +- src/react/hooks/useSuspenseQuery.ts | 90 +- src/react/hooks/useSyncExternalStore.ts | 168 +- src/react/index.ts | 16 +- src/react/parser/__tests__/parser.test.ts | 38 +- src/react/parser/index.ts | 66 +- src/react/ssr/RenderPromises.ts | 54 +- src/react/ssr/__tests__/useLazyQuery.test.tsx | 40 +- src/react/ssr/__tests__/useQuery.test.tsx | 197 +- .../ssr/__tests__/useReactiveVar.test.tsx | 27 +- src/react/ssr/getDataFromTree.ts | 36 +- src/react/ssr/index.ts | 6 +- src/react/ssr/renderToStringWithData.ts | 8 +- src/react/types/types.ts | 185 +- src/testing/matchers/index.d.ts | 18 +- src/testing/matchers/index.ts | 6 +- .../matchers/toHaveSuspenseCacheEntryUsing.ts | 22 +- src/testing/matchers/toMatchDocument.ts | 20 +- src/utilities/common/__tests__/omitDeep.ts | 120 +- .../common/__tests__/stripTypename.ts | 52 +- src/utilities/common/omitDeep.ts | 4 +- src/utilities/common/stripTypename.ts | 4 +- src/utilities/globals/invariantWrappers.ts | 16 +- src/utilities/graphql/DocumentTransform.ts | 12 +- .../graphql/__tests__/DocumentTransform.ts | 94 +- src/utilities/graphql/operations.ts | 12 +- src/utilities/graphql/print.ts | 4 +- src/utilities/promises/decoration.ts | 22 +- src/utilities/types/DeepOmit.ts | 2 +- src/utilities/types/DeepPartial.ts | 2 +- tsconfig.json | 4 +- 188 files changed, 16401 insertions(+), 14589 deletions(-) create mode 100644 .prettierrc diff --git a/.attw.json b/.attw.json index 7e5b3c23693..a1e00f328b4 100644 --- a/.attw.json +++ b/.attw.json @@ -1,6 +1,3 @@ { - "ignoreRules": [ - "false-esm", - "cjs-resolves-to-esm" - ] + "ignoreRules": ["false-esm", "cjs-resolves-to-esm"] } diff --git a/.eslintrc b/.eslintrc index 02a8f6d9027..182b279483c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -24,11 +24,14 @@ "files": ["**/*.ts", "**/*.tsx"], "excludedFiles": ["**/__tests__/**/*.*"], "rules": { - "@typescript-eslint/consistent-type-imports": ["error", { - "prefer": "type-imports", - "disallowTypeAnnotations": false, - "fixStyle": "separate-type-imports" - }], + "@typescript-eslint/consistent-type-imports": [ + "error", + { + "prefer": "type-imports", + "disallowTypeAnnotations": false, + "fixStyle": "separate-type-imports" + } + ], "@typescript-eslint/no-import-type-side-effects": "error", "no-restricted-syntax": [ "error", diff --git a/.github/workflows/close-stale-issues.yml b/.github/workflows/close-stale-issues.yml index 0e533202e5e..5d54d58ea6d 100644 --- a/.github/workflows/close-stale-issues.yml +++ b/.github/workflows/close-stale-issues.yml @@ -7,7 +7,7 @@ name: Mark stale issues and pull requests on: schedule: - - cron: '15 5 * * *' + - cron: "15 5 * * *" jobs: stale: @@ -19,161 +19,161 @@ jobs: pull-requests: write steps: - - name: Close Stale Issues - uses: actions/stale@v8.0.0 - with: - # # Token for the repository. Can be passed in using `{{ secrets.GITHUB_TOKEN }}`. - # repo-token: # optional, default is ${{ github.token }} + - name: Close Stale Issues + uses: actions/stale@v8.0.0 + with: + # # Token for the repository. Can be passed in using `{{ secrets.GITHUB_TOKEN }}`. + # repo-token: # optional, default is ${{ github.token }} - # # The message to post on the issue when tagging it. If none provided, will not mark issues stale. - #. stale-issue-message: # optional + # # The message to post on the issue when tagging it. If none provided, will not mark issues stale. + #. stale-issue-message: # optional - # # The message to post on the pull request when tagging it. If none provided, will not mark pull requests stale. - # stale-pr-message: # optional + # # The message to post on the pull request when tagging it. If none provided, will not mark pull requests stale. + # stale-pr-message: # optional - # The message to post on the issue when closing it. If none provided, will not comment when closing an issue. - close-issue-message: "We're closing this issue now but feel free to ping the maintainers or open a new issue if you still need support. Thank you!" + # The message to post on the issue when closing it. If none provided, will not comment when closing an issue. + close-issue-message: "We're closing this issue now but feel free to ping the maintainers or open a new issue if you still need support. Thank you!" - # # The message to post on the pull request when closing it. If none provided, will not comment when closing a pull requests. - # close-pr-message: # optional + # # The message to post on the pull request when closing it. If none provided, will not comment when closing a pull requests. + # close-pr-message: # optional - # The number of days old an issue or a pull request can be before marking it stale. Set to -1 to never mark issues or pull requests as stale automatically. - days-before-stale: -1 + # The number of days old an issue or a pull request can be before marking it stale. Set to -1 to never mark issues or pull requests as stale automatically. + days-before-stale: -1 - # The number of days old an issue can be before marking it stale. Set to -1 to never mark issues as stale automatically. Override "days-before-stale" option regarding only the issues. - days-before-issue-stale: -1 + # The number of days old an issue can be before marking it stale. Set to -1 to never mark issues as stale automatically. Override "days-before-stale" option regarding only the issues. + days-before-issue-stale: -1 - # The number of days old a pull request can be before marking it stale. Set to -1 to never mark pull requests as stale automatically. Override "days-before-stale" option regarding only the pull requests. - days-before-pr-stale: -1 + # The number of days old a pull request can be before marking it stale. Set to -1 to never mark pull requests as stale automatically. Override "days-before-stale" option regarding only the pull requests. + days-before-pr-stale: -1 - # The number of days to wait to close an issue or a pull request after it being marked stale. Set to -1 to never close stale issues or pull requests. - days-before-close: -1 + # The number of days to wait to close an issue or a pull request after it being marked stale. Set to -1 to never close stale issues or pull requests. + days-before-close: -1 - # The number of days to wait to close an issue after it being marked stale. Set to -1 to never close stale issues. Override "days-before-close" option regarding only the issues. - days-before-issue-close: 30 + # The number of days to wait to close an issue after it being marked stale. Set to -1 to never close stale issues. Override "days-before-close" option regarding only the issues. + days-before-issue-close: 30 - # The number of days to wait to close a pull request after it being marked stale. Set to -1 to never close stale pull requests. Override "days-before-close" option regarding only the pull requests. - days-before-pr-close: -1 + # The number of days to wait to close a pull request after it being marked stale. Set to -1 to never close stale pull requests. Override "days-before-close" option regarding only the pull requests. + days-before-pr-close: -1 - # The label to apply when an issue is stale. - stale-issue-label: "🏓 awaiting-contributor-response" + # The label to apply when an issue is stale. + stale-issue-label: "🏓 awaiting-contributor-response" - # # The label to apply when an issue is closed. - # close-issue-label: # optional + # # The label to apply when an issue is closed. + # close-issue-label: # optional - # # The labels that mean an issue is exempt from being marked stale. Separate multiple labels with commas (eg. "label1,label2"). - # exempt-issue-labels: # optional, default is + # # The labels that mean an issue is exempt from being marked stale. Separate multiple labels with commas (eg. "label1,label2"). + # exempt-issue-labels: # optional, default is - # The reason to use when closing an issue. - close-issue-reason: not_planned + # The reason to use when closing an issue. + close-issue-reason: not_planned - # # The label to apply when a pull request is stale. - # stale-pr-label: # optional, default is Stale + # # The label to apply when a pull request is stale. + # stale-pr-label: # optional, default is Stale - # # The label to apply when a pull request is closed. - # close-pr-label: # optional + # # The label to apply when a pull request is closed. + # close-pr-label: # optional - # # The labels that mean a pull request is exempt from being marked as stale. Separate multiple labels with commas (eg. "label1,label2"). - # exempt-pr-labels: # optional, default is + # # The labels that mean a pull request is exempt from being marked as stale. Separate multiple labels with commas (eg. "label1,label2"). + # exempt-pr-labels: # optional, default is - # # The milestones that mean an issue or a pull request is exempt from being marked as stale. Separate multiple milestones with commas (eg. "milestone1,milestone2"). - # exempt-milestones: # optional, default is + # # The milestones that mean an issue or a pull request is exempt from being marked as stale. Separate multiple milestones with commas (eg. "milestone1,milestone2"). + # exempt-milestones: # optional, default is - # # The milestones that mean an issue is exempt from being marked as stale. Separate multiple milestones with commas (eg. "milestone1,milestone2"). Override "exempt-milestones" option regarding only the issues. - # exempt-issue-milestones: # optional, default is + # # The milestones that mean an issue is exempt from being marked as stale. Separate multiple milestones with commas (eg. "milestone1,milestone2"). Override "exempt-milestones" option regarding only the issues. + # exempt-issue-milestones: # optional, default is - # # The milestones that mean a pull request is exempt from being marked as stale. Separate multiple milestones with commas (eg. "milestone1,milestone2"). Override "exempt-milestones" option regarding only the pull requests. - # exempt-pr-milestones: # optional, default is + # # The milestones that mean a pull request is exempt from being marked as stale. Separate multiple milestones with commas (eg. "milestone1,milestone2"). Override "exempt-milestones" option regarding only the pull requests. + # exempt-pr-milestones: # optional, default is - # # Exempt all issues and pull requests with milestones from being marked as stale. Default to false. - # exempt-all-milestones: # optional, default is false + # # Exempt all issues and pull requests with milestones from being marked as stale. Default to false. + # exempt-all-milestones: # optional, default is false - # # Exempt all issues with milestones from being marked as stale. Override "exempt-all-milestones" option regarding only the issues. - # exempt-all-issue-milestones: # optional, default is + # # Exempt all issues with milestones from being marked as stale. Override "exempt-all-milestones" option regarding only the issues. + # exempt-all-issue-milestones: # optional, default is - # # Exempt all pull requests with milestones from being marked as stale. Override "exempt-all-milestones" option regarding only the pull requests. - # exempt-all-pr-milestones: # optional, default is + # # Exempt all pull requests with milestones from being marked as stale. Override "exempt-all-milestones" option regarding only the pull requests. + # exempt-all-pr-milestones: # optional, default is - # # Only issues or pull requests with all of these labels are checked if stale. Defaults to `` (disabled) and can be a comma-separated list of labels. - # only-labels: # optional, default is + # # Only issues or pull requests with all of these labels are checked if stale. Defaults to `` (disabled) and can be a comma-separated list of labels. + # only-labels: # optional, default is - # # Only issues or pull requests with at least one of these labels are checked if stale. Defaults to `` (disabled) and can be a comma-separated list of labels. - # any-of-labels: # optional, default is + # # Only issues or pull requests with at least one of these labels are checked if stale. Defaults to `` (disabled) and can be a comma-separated list of labels. + # any-of-labels: # optional, default is - # Only issues with at least one of these labels are checked if stale. Defaults to `` (disabled) and can be a comma-separated list of labels. Override "any-of-labels" option regarding only the issues. - any-of-issue-labels: "🏓 awaiting-contributor-response" + # Only issues with at least one of these labels are checked if stale. Defaults to `` (disabled) and can be a comma-separated list of labels. Override "any-of-labels" option regarding only the issues. + any-of-issue-labels: "🏓 awaiting-contributor-response" - # # Only pull requests with at least one of these labels are checked if stale. Defaults to `` (disabled) and can be a comma-separated list of labels. Override "any-of-labels" option regarding only the pull requests. - # any-of-pr-labels: # optional, default is + # # Only pull requests with at least one of these labels are checked if stale. Defaults to `` (disabled) and can be a comma-separated list of labels. Override "any-of-labels" option regarding only the pull requests. + # any-of-pr-labels: # optional, default is - # # Only issues with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels. Override "only-labels" option regarding only the issues. - # only-issue-labels: # optional, default is + # # Only issues with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels. Override "only-labels" option regarding only the issues. + # only-issue-labels: # optional, default is - # # Only pull requests with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels. Override "only-labels" option regarding only the pull requests. - # only-pr-labels: # optional, default is + # # Only pull requests with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels. Override "only-labels" option regarding only the pull requests. + # only-pr-labels: # optional, default is - # The maximum number of operations per run, used to control rate limiting (GitHub API CRUD related). - operations-per-run: 500 # optional, default is 30 + # The maximum number of operations per run, used to control rate limiting (GitHub API CRUD related). + operations-per-run: 500 # optional, default is 30 - # Remove stale labels from issues and pull requests when they are updated or commented on. - remove-stale-when-updated: false + # Remove stale labels from issues and pull requests when they are updated or commented on. + remove-stale-when-updated: false - # Remove stale labels from issues when they are updated or commented on. Override "remove-stale-when-updated" option regarding only the issues. - remove-issue-stale-when-updated: true + # Remove stale labels from issues when they are updated or commented on. Override "remove-stale-when-updated" option regarding only the issues. + remove-issue-stale-when-updated: true - # Remove stale labels from pull requests when they are updated or commented on. Override "remove-stale-when-updated" option regarding only the pull requests. - remove-pr-stale-when-updated: false + # Remove stale labels from pull requests when they are updated or commented on. Override "remove-stale-when-updated" option regarding only the pull requests. + remove-pr-stale-when-updated: false - # Run the processor in debug mode without actually performing any operations on live issues. - debug-only: false + # Run the processor in debug mode without actually performing any operations on live issues. + debug-only: false - # # The order to get issues or pull requests. Defaults to false, which is descending. - # ascending: # optional, default is false + # # The order to get issues or pull requests. Defaults to false, which is descending. + # ascending: # optional, default is false - # Delete the git branch after closing a stale pull request. - delete-branch: false + # Delete the git branch after closing a stale pull request. + delete-branch: false - # # The date used to skip the stale action on issue/pull request created before it (ISO 8601 or RFC 2822). - # start-date: # optional, default is + # # The date used to skip the stale action on issue/pull request created before it (ISO 8601 or RFC 2822). + # start-date: # optional, default is - # # The assignees which exempt an issue or a pull request from being marked as stale. Separate multiple assignees with commas (eg. "user1,user2"). - # exempt-assignees: # optional, default is + # # The assignees which exempt an issue or a pull request from being marked as stale. Separate multiple assignees with commas (eg. "user1,user2"). + # exempt-assignees: # optional, default is - # # The assignees which exempt an issue from being marked as stale. Separate multiple assignees with commas (eg. "user1,user2"). Override "exempt-assignees" option regarding only the issues. - # exempt-issue-assignees: # optional, default is + # # The assignees which exempt an issue from being marked as stale. Separate multiple assignees with commas (eg. "user1,user2"). Override "exempt-assignees" option regarding only the issues. + # exempt-issue-assignees: # optional, default is - # # The assignees which exempt a pull request from being marked as stale. Separate multiple assignees with commas (eg. "user1,user2"). Override "exempt-assignees" option regarding only the pull requests. - # exempt-pr-assignees: # optional, default is + # # The assignees which exempt a pull request from being marked as stale. Separate multiple assignees with commas (eg. "user1,user2"). Override "exempt-assignees" option regarding only the pull requests. + # exempt-pr-assignees: # optional, default is - # # Exempt all issues and pull requests with assignees from being marked as stale. Default to false. - # exempt-all-assignees: # optional, default is false + # # Exempt all issues and pull requests with assignees from being marked as stale. Default to false. + # exempt-all-assignees: # optional, default is false - # Exempt all issues with assignees from being marked as stale. Override "exempt-all-assignees" option regarding only the issues. - exempt-all-issue-assignees: true + # Exempt all issues with assignees from being marked as stale. Override "exempt-all-assignees" option regarding only the issues. + exempt-all-issue-assignees: true - # # Exempt all pull requests with assignees from being marked as stale. Override "exempt-all-assignees" option regarding only the pull requests. - # exempt-all-pr-assignees: # optional, default is + # # Exempt all pull requests with assignees from being marked as stale. Override "exempt-all-assignees" option regarding only the pull requests. + # exempt-all-pr-assignees: # optional, default is - # # Exempt draft pull requests from being marked as stale. Default to false. - # exempt-draft-pr: # optional, default is false + # # Exempt draft pull requests from being marked as stale. Default to false. + # exempt-draft-pr: # optional, default is false - # # Display some statistics at the end regarding the stale workflow (only when the logs are enabled). - enable-statistics: true + # # Display some statistics at the end regarding the stale workflow (only when the logs are enabled). + enable-statistics: true - # # A comma delimited list of labels to add when a stale issue or pull request receives activity and has the stale-issue-label or stale-pr-label removed from it. - # labels-to-add-when-unstale: # optional, default is + # # A comma delimited list of labels to add when a stale issue or pull request receives activity and has the stale-issue-label or stale-pr-label removed from it. + # labels-to-add-when-unstale: # optional, default is - # A comma delimited list of labels to remove when a stale issue or pull request receives activity and has the stale-issue-label or stale-pr-label removed from it. - labels-to-remove-when-unstale: "🏓 awaiting-contributor-response" + # A comma delimited list of labels to remove when a stale issue or pull request receives activity and has the stale-issue-label or stale-pr-label removed from it. + labels-to-remove-when-unstale: "🏓 awaiting-contributor-response" - # Any update (update/comment) can reset the stale idle time on the issues and pull requests. - ignore-updates: false + # Any update (update/comment) can reset the stale idle time on the issues and pull requests. + ignore-updates: false - # Any update (update/comment) can reset the stale idle time on the issues. Override "ignore-updates" option regarding only the issues. - ignore-issue-updates: false + # Any update (update/comment) can reset the stale idle time on the issues. Override "ignore-updates" option regarding only the issues. + ignore-issue-updates: false - # Any update (update/comment) can reset the stale idle time on the pull requests. Override "ignore-updates" option regarding only the pull requests. - ignore-pr-updates: false + # Any update (update/comment) can reset the stale idle time on the pull requests. Override "ignore-updates" option regarding only the pull requests. + ignore-pr-updates: false - # # Only the issues or the pull requests with an assignee will be marked as stale automatically. - # include-only-assigned: # optional, default is false + # # Only the issues or the pull requests with an assignee will be marked as stale automatically. + # include-only-assigned: # optional, default is false diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 65ae0c4f75e..8c050ae5b5e 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -1,8 +1,8 @@ -name: 'Lock Threads' +name: "Lock Threads" on: schedule: - - cron: '0 0 * * *' + - cron: "0 0 * * *" permissions: issues: write @@ -20,12 +20,12 @@ jobs: with: github-token: ${{ github.token }} log-output: true - issue-inactive-days: '30' - exclude-any-issue-labels: 'discussion' + issue-inactive-days: "30" + exclude-any-issue-labels: "discussion" issue-comment: > This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. For general questions, we recommend using [StackOverflow](https://stackoverflow.com/questions/tagged/apollo-client) or our [discord server](https://discord.gg/graphos). - pr-inactive-days: '30' - exclude-any-pr-labels: 'discussion' + pr-inactive-days: "30" + exclude-any-pr-labels: "discussion" diff --git a/.prettierignore b/.prettierignore index 3f7a5b85c01..2175bae9705 100644 --- a/.prettierignore +++ b/.prettierignore @@ -15,95 +15,219 @@ # Ignore all mdx & md files: *.mdx *.md +*.har +*.snap -# Do not format anything automatically except files listed below -/* +node_modules/ +.yalc/ +.next/ -!config/ -config/* -!config/processInvariants.ts +# Ignore all files in /scripts directory +/scripts -!integration-tests/ -!integration-tests/** - -##### PATHS TO BE FORMATTED ##### -!src/ -src/* -!src/react/ -src/react/* - -!src/dev - -# Allow src/react/cache/ApolloProvider -!src/react/context/ -src/react/context/* -!src/react/context/ApolloProvider.tsx -!src/react/context/ApolloContext.ts -!src/react/context/__tests__/ -src/react/context/__tests__/* -!src/react/context/__tests__/ApolloProvider.test.tsx - -# Allow src/react/cache -!src/react/cache/ - -# Allow certain core files -!src/core -src/core/* -!src/core/__tests__/ -src/core/tests/* -!src/core/__tests__/equalByQuery.ts -!src/core/equalByQuery.ts - -# Allowed utilities -!src/utilities/ -src/utilities/* -!src/utilities/promises/ -!src/utilities/types/ -src/utilities/types/* -!src/utilities/types/DeepOmit.ts -!src/utilities/types/DeepPartial.ts -!src/utilities/types/Primitive.ts -!src/utilities/common -src/utilities/common/* -!src/utilities/common/stripTypename.ts -!src/utilities/common/omitDeep.ts -!src/utilities/common/tap.ts -!src/utilities/common/__tests__/ -src/utilities/common/__tests__/* -!src/utilities/common/__tests__/omitDeep.ts -!src/utilities/common/__tests__/stripTypename.ts -!src/utilities/globals -src/utilities/globals/* -!src/utilities/globals/invariantWrappers.ts -!src/utilities/graphql -src/utilities/graphql/* -!src/utilities/graphql/operations.ts -!src/utilities/graphql/print.ts -!src/utilities/graphql/DocumentTransform.ts -!src/utilities/graphql/__tests__/ -src/utilities/graphql/__tests__/* -!src/utilities/graphql/__tests__/DocumentTransform.ts - -# Allowed links -!src/link -src/link/* -!src/link/remove-typename - -## Allowed React Hooks -!src/react/hooks/ -src/react/hooks/* -!src/react/hooks/internal -!src/react/hooks/useSuspenseCache.ts -!src/react/hooks/useSuspenseQuery.ts -!src/react/hooks/useBackgroundQuery.ts - -## Allowed React hook tests -!src/react/hooks/__tests__/ -src/react/hooks/__tests__/* -!src/react/hooks/__tests__/useSuspenseQuery.test.tsx -!src/react/hooks/__tests__/useBackgroundQuery.test.tsx - -## Allowed testing utils -!src/testing -src/testing/* -!src/testing/matchers +/src/__tests__/ApolloClient.ts +/src/__tests__/client.ts +/src/__tests__/exports.ts +/src/__tests__/fetchMore.ts +/src/__tests__/graphqlSubscriptions.ts +/src/__tests__/local-state/export.ts +/src/__tests__/local-state/general.ts +/src/__tests__/local-state/resolvers.ts +/src/__tests__/local-state/subscriptions.ts +/src/__tests__/mutationResults.ts +/src/__tests__/optimistic.ts +/src/__tests__/refetchQueries.ts +/src/__tests__/resultCacheCleaning.ts +/src/__tests__/subscribeToMore.ts +/src/cache/core/__tests__/cache.ts +/src/cache/core/cache.ts +/src/cache/core/types/Cache.ts +/src/cache/core/types/DataProxy.ts +/src/cache/core/types/common.ts +/src/cache/index.ts +/src/cache/inmemory/__tests__/cache.ts +/src/cache/inmemory/__tests__/diffAgainstStore.ts +/src/cache/inmemory/__tests__/entityStore.ts +/src/cache/inmemory/__tests__/fragmentMatcher.ts +/src/cache/inmemory/__tests__/fragmentRegistry.ts +/src/cache/inmemory/__tests__/helpers.ts +/src/cache/inmemory/__tests__/key-extractor.ts +/src/cache/inmemory/__tests__/object-canon.ts +/src/cache/inmemory/__tests__/optimistic.ts +/src/cache/inmemory/__tests__/policies.ts +/src/cache/inmemory/__tests__/readFromStore.ts +/src/cache/inmemory/__tests__/recordingCache.ts +/src/cache/inmemory/__tests__/roundtrip.ts +/src/cache/inmemory/__tests__/writeToStore.ts +/src/cache/inmemory/entityStore.ts +/src/cache/inmemory/fixPolyfills.native.ts +/src/cache/inmemory/fixPolyfills.ts +/src/cache/inmemory/fragmentRegistry.ts +/src/cache/inmemory/helpers.ts +/src/cache/inmemory/inMemoryCache.ts +/src/cache/inmemory/key-extractor.ts +/src/cache/inmemory/object-canon.ts +/src/cache/inmemory/policies.ts +/src/cache/inmemory/reactiveVars.ts +/src/cache/inmemory/readFromStore.ts +/src/cache/inmemory/types.ts +/src/cache/inmemory/writeToStore.ts +/src/config/jest/setup.ts +/src/core/ApolloClient.ts +/src/core/LocalState.ts +/src/core/ObservableQuery.ts +/src/core/QueryInfo.ts +/src/core/QueryManager.ts +/src/core/__tests__/LocalState.ts +/src/core/__tests__/ObservableQuery.ts +/src/core/__tests__/QueryManager/index.ts +/src/core/__tests__/QueryManager/links.ts +/src/core/__tests__/QueryManager/multiple-results.ts +/src/core/__tests__/QueryManager/recycler.ts +/src/core/__tests__/fetchPolicies.ts +/src/core/index.ts +/src/core/networkStatus.ts +/src/core/types.ts +/src/core/watchQueryOptions.ts +/src/errors/__tests__/ApolloError.ts +/src/errors/index.ts +/src/index.ts +/src/invariantErrorCodes.ts +/src/link/batch-http/__tests__/batchHttpLink.ts +/src/link/batch-http/batchHttpLink.ts +/src/link/batch-http/index.ts +/src/link/batch/__tests__/batchLink.ts +/src/link/batch/batchLink.ts +/src/link/batch/batching.ts +/src/link/batch/index.ts +/src/link/context/__tests__/index.ts +/src/link/context/index.ts +/src/link/core/ApolloLink.ts +/src/link/core/__tests__/ApolloLink.ts +/src/link/core/concat.ts +/src/link/core/empty.ts +/src/link/core/execute.ts +/src/link/core/from.ts +/src/link/core/index.ts +/src/link/core/split.ts +/src/link/core/types.ts +/src/link/error/__tests__/index.ts +/src/link/error/index.ts +/src/link/http/HttpLink.ts +/src/link/http/__tests__/HttpLink.ts +/src/link/http/__tests__/checkFetcher.ts +/src/link/http/__tests__/headerNormalization.ts +/src/link/http/__tests__/helpers.ts +/src/link/http/__tests__/parseAndCheckHttpResponse.ts +/src/link/http/__tests__/responseIterator.ts +/src/link/http/__tests__/responseIteratorNoAsyncIterator.ts +/src/link/http/__tests__/selectHttpOptionsAndBody.ts +/src/link/http/__tests__/selectURI.ts +/src/link/http/__tests__/serializeFetchParameter.ts +/src/link/http/checkFetcher.ts +/src/link/http/createHttpLink.ts +/src/link/http/createSignalIfSupported.ts +/src/link/http/index.ts +/src/link/http/iterators/async.ts +/src/link/http/iterators/nodeStream.ts +/src/link/http/iterators/promise.ts +/src/link/http/iterators/reader.ts +/src/link/http/parseAndCheckHttpResponse.ts +/src/link/http/responseIterator.ts +/src/link/http/rewriteURIForGET.ts +/src/link/http/selectHttpOptionsAndBody.ts +/src/link/http/selectURI.ts +/src/link/http/serializeFetchParameter.ts +/src/link/persisted-queries/__tests__/persisted-queries.test.ts +/src/link/persisted-queries/__tests__/react.test.tsx +/src/link/persisted-queries/index.ts +/src/link/retry/__tests__/delayFunction.ts +/src/link/retry/__tests__/retryFunction.ts +/src/link/retry/__tests__/retryLink.ts +/src/link/retry/delayFunction.ts +/src/link/retry/index.ts +/src/link/retry/retryFunction.ts +/src/link/retry/retryLink.ts +/src/link/schema/__tests__/schemaLink.ts +/src/link/schema/index.ts +/src/link/subscriptions/__tests__/graphqlWsLink.ts +/src/link/subscriptions/index.ts +/src/link/utils/__tests__/filterOperationVariables.ts +/src/link/utils/__tests__/fromError.ts +/src/link/utils/__tests__/fromPromise.ts +/src/link/utils/__tests__/toPromise.ts +/src/link/utils/__tests__/validateOperation.ts +/src/link/utils/createOperation.ts +/src/link/utils/filterOperationVariables.ts +/src/link/utils/fromError.ts +/src/link/utils/fromPromise.ts +/src/link/utils/index.ts +/src/link/utils/throwServerError.ts +/src/link/utils/toPromise.ts +/src/link/utils/transformOperation.ts +/src/link/utils/validateOperation.ts +/src/link/ws/__tests__/webSocketLink.ts +/src/link/ws/index.ts +/src/testing/core/index.ts +/src/testing/core/itAsync.ts +/src/testing/core/mocking/mockClient.ts +/src/testing/core/mocking/mockFetch.ts +/src/testing/core/mocking/mockLink.ts +/src/testing/core/mocking/mockQueryManager.ts +/src/testing/core/mocking/mockSubscriptionLink.ts +/src/testing/core/mocking/mockWatchQuery.ts +/src/testing/core/observableToPromise.ts +/src/testing/core/subscribeAndCount.ts +/src/testing/core/wait.ts +/src/testing/core/withConsoleSpy.ts +/src/testing/core/wrap.ts +/src/testing/index.ts +/src/testing/react/MockedProvider.tsx +/src/testing/react/__tests__/MockedProvider.test.tsx +/src/testing/react/__tests__/mockSubscriptionLink.test.tsx +/src/utilities/common/__tests__/canUse.ts +/src/utilities/common/__tests__/cloneDeep.ts +/src/utilities/common/__tests__/compact.ts +/src/utilities/common/__tests__/maybeDeepFeeze.ts +/src/utilities/common/__tests__/mergeDeep.ts +/src/utilities/common/arrays.ts +/src/utilities/common/canUse.ts +/src/utilities/common/cloneDeep.ts +/src/utilities/common/compact.ts +/src/utilities/common/errorHandling.ts +/src/utilities/common/filterInPlace.ts +/src/utilities/common/incrementalResult.ts +/src/utilities/common/makeUniqueId.ts +/src/utilities/common/maybeDeepFreeze.ts +/src/utilities/common/mergeDeep.ts +/src/utilities/common/mergeOptions.ts +/src/utilities/common/objects.ts +/src/utilities/common/responseIterator.ts +/src/utilities/common/stringifyForDisplay.ts +/src/utilities/globals/global.ts +/src/utilities/globals/index.ts +/src/utilities/globals/maybe.ts +/src/utilities/graphql/__tests__/directives.ts +/src/utilities/graphql/__tests__/fragments.ts +/src/utilities/graphql/__tests__/getFromAST.ts +/src/utilities/graphql/__tests__/storeUtils.ts +/src/utilities/graphql/__tests__/transform.ts +/src/utilities/graphql/directives.ts +/src/utilities/graphql/fragments.ts +/src/utilities/graphql/getFromAST.ts +/src/utilities/graphql/storeUtils.ts +/src/utilities/graphql/transform.ts +/src/utilities/index.ts +/src/utilities/observables/Concast.ts +/src/utilities/observables/Observable.ts +/src/utilities/observables/__tests__/Concast.ts +/src/utilities/observables/__tests__/Observable.ts +/src/utilities/observables/__tests__/asyncMap.ts +/src/utilities/observables/__tests__/subclassing.ts +/src/utilities/observables/asyncMap.ts +/src/utilities/observables/iteration.ts +/src/utilities/observables/subclassing.ts +/src/utilities/policies/__tests__/relayStylePagination.test.ts +/src/utilities/policies/pagination.ts +/src/utilities/types/IsStrictlyAny.ts +/src/version.ts diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000000..c21d016783a --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "bracketSpacing": true, + "printWidth": 80, + "semi": true, + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "es5" +} diff --git a/.size-limit.cjs b/.size-limit.cjs index 653df42d84e..ab25a3df44e 100644 --- a/.size-limit.cjs +++ b/.size-limit.cjs @@ -1,16 +1,16 @@ const checks = [ { path: "dist/apollo-client.min.cjs", - limit: "38056" + limit: "38056", }, { path: "dist/main.cjs", - import: "{ ApolloClient, InMemoryCache, HttpLink }" + import: "{ ApolloClient, InMemoryCache, HttpLink }", }, { path: "dist/index.js", import: "{ ApolloClient, InMemoryCache, HttpLink }", - limit: "31971" + limit: "31971", }, ...[ "ApolloProvider", @@ -21,38 +21,50 @@ const checks = [ "useSuspenseQuery", "useBackgroundQuery", "useReadQuery", - "useFragment" + "useFragment", ].map((name) => ({ path: "dist/react/index.js", import: `{ ${name} }` })), -].map((config) => ({ - ...config, - name: config.name || config.import ? `import ${config.import} from "${config.path}"` : config.path, - ignore: [ - ...(config.ignore || []), - "react", - "react-dom", - "@graphql-typed-document-node/core", - "@wry/context", - "@wry/equality", - "@wry/trie", - "graphql-tag", - "hoist-non-react-statics", - "optimism", - "prop-types", - "response-iterator", - "symbol-observable", - "ts-invariant", - "tslib", - "zen-observable-ts" - ], -})).flatMap((value) => value.path == "dist/apollo-client.min.cjs" ? value : [{...value, limit: undefined}, { - ...value, - name: `${value.name} (production)`, - modifyEsbuildConfig(config){ - config.define = { - "globalThis.__DEV__": `false`, - } - return config - } -}]); +] + .map((config) => ({ + ...config, + name: + config.name || config.import + ? `import ${config.import} from "${config.path}"` + : config.path, + ignore: [ + ...(config.ignore || []), + "react", + "react-dom", + "@graphql-typed-document-node/core", + "@wry/context", + "@wry/equality", + "@wry/trie", + "graphql-tag", + "hoist-non-react-statics", + "optimism", + "prop-types", + "response-iterator", + "symbol-observable", + "ts-invariant", + "tslib", + "zen-observable-ts", + ], + })) + .flatMap((value) => + value.path == "dist/apollo-client.min.cjs" + ? value + : [ + { ...value, limit: undefined }, + { + ...value, + name: `${value.name} (production)`, + modifyEsbuildConfig(config) { + config.define = { + "globalThis.__DEV__": `false`, + }; + return config; + }, + }, + ] + ); module.exports = checks; diff --git a/.vscode/launch.json b/.vscode/launch.json index 460c43a7a7d..6cdb71bc99d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,9 +5,7 @@ "name": "Attach to Node.js inspector", "port": 9229, "request": "attach", - "skipFiles": [ - "/**" - ], + "skipFiles": ["/**"], "type": "pwa-node" }, { @@ -15,16 +13,12 @@ "request": "launch", "name": "Jest Current File", "program": "${workspaceFolder}/node_modules/.bin/jest", - "args": [ - "${relativeFile}", - "--config", - "./config/jest.config.js" - ], + "args": ["${relativeFile}", "--config", "./config/jest.config.js"], "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", "disableOptimisticBPs": true, "windows": { - "program": "${workspaceFolder}/node_modules/jest/bin/jest", + "program": "${workspaceFolder}/node_modules/jest/bin/jest" } } ] diff --git a/.vscode/settings.json b/.vscode/settings.json index e343acdb09c..dddb0e3d487 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,8 +5,6 @@ "files.trimTrailingWhitespace": true, "files.insertFinalNewline": true, "typescript.tsdk": "node_modules/typescript/lib", - "cSpell.enableFiletypes": [ - "mdx" - ], - "jest.jestCommandLine": "node_modules/.bin/jest --config ./config/jest.config.js --ignoreProjects 'ReactDOM 17' --runInBand", + "cSpell.enableFiletypes": ["mdx"], + "jest.jestCommandLine": "node_modules/.bin/jest --config ./config/jest.config.js --ignoreProjects 'ReactDOM 17' --runInBand" } diff --git a/config/bundlesize.ts b/config/bundlesize.ts index d0529f47439..4a5ad18177b 100644 --- a/config/bundlesize.ts +++ b/config/bundlesize.ts @@ -9,15 +9,13 @@ const minPath = join(__dirname, "..", minFile); const gzipByteLen = gzipSync(readFileSync(minPath)).byteLength; const overLimit = gzipByteLen > gzipBundleByteLengthLimit; -const message = `Minified + GZIP-encoded bundle size for ${ - minFile -} = ${ - bytes(gzipByteLen, { unit: "KB" }) -}, ${ - overLimit ? "exceeding" : "under" -} limit ${ - bytes(gzipBundleByteLengthLimit, { unit: "KB" }) -}`; +const message = `Minified + GZIP-encoded bundle size for ${minFile} = ${bytes( + gzipByteLen, + { unit: "KB" } +)}, ${overLimit ? "exceeding" : "under"} limit ${bytes( + gzipBundleByteLengthLimit, + { unit: "KB" } +)}`; if (overLimit) { throw new Error(message); diff --git a/config/entryPoints.js b/config/entryPoints.js index 8afa1c9af0c..dbd41ad4d64 100644 --- a/config/entryPoints.js +++ b/config/entryPoints.js @@ -1,46 +1,46 @@ const entryPoints = [ { dirs: [], bundleName: "main" }, - { dirs: ['cache'] }, - { dirs: ['core'] }, - { dirs: ['dev'] }, - { dirs: ['errors'] }, - { dirs: ['link', 'batch'] }, - { dirs: ['link', 'batch-http'] }, - { dirs: ['link', 'context'] }, - { dirs: ['link', 'core'] }, - { dirs: ['link', 'error'] }, - { dirs: ['link', 'http'] }, - { dirs: ['link', 'persisted-queries'] }, - { dirs: ['link', 'retry'] }, - { dirs: ['link', 'remove-typename'] }, - { dirs: ['link', 'schema'] }, - { dirs: ['link', 'subscriptions'] }, - { dirs: ['link', 'utils'] }, - { dirs: ['link', 'ws'] }, - { dirs: ['react'] }, - { dirs: ['react', 'components'] }, - { dirs: ['react', 'context'] }, - { dirs: ['react', 'hoc'] }, - { dirs: ['react', 'hooks'] }, - { dirs: ['react', 'parser'] }, - { dirs: ['react', 'ssr'] }, - { dirs: ['testing'], extensions: [".js", ".jsx"] }, - { dirs: ['testing', 'core'] }, - { dirs: ['utilities'] }, - { dirs: ['utilities', 'globals'], sideEffects: true }, + { dirs: ["cache"] }, + { dirs: ["core"] }, + { dirs: ["dev"] }, + { dirs: ["errors"] }, + { dirs: ["link", "batch"] }, + { dirs: ["link", "batch-http"] }, + { dirs: ["link", "context"] }, + { dirs: ["link", "core"] }, + { dirs: ["link", "error"] }, + { dirs: ["link", "http"] }, + { dirs: ["link", "persisted-queries"] }, + { dirs: ["link", "retry"] }, + { dirs: ["link", "remove-typename"] }, + { dirs: ["link", "schema"] }, + { dirs: ["link", "subscriptions"] }, + { dirs: ["link", "utils"] }, + { dirs: ["link", "ws"] }, + { dirs: ["react"] }, + { dirs: ["react", "components"] }, + { dirs: ["react", "context"] }, + { dirs: ["react", "hoc"] }, + { dirs: ["react", "hooks"] }, + { dirs: ["react", "parser"] }, + { dirs: ["react", "ssr"] }, + { dirs: ["testing"], extensions: [".js", ".jsx"] }, + { dirs: ["testing", "core"] }, + { dirs: ["utilities"] }, + { dirs: ["utilities", "globals"], sideEffects: true }, ]; const lookupTrie = Object.create(null); -entryPoints.forEach(info => { +entryPoints.forEach((info) => { let node = lookupTrie; - info.dirs.forEach(dir => { + info.dirs.forEach((dir) => { const dirs = node.dirs || (node.dirs = Object.create(null)); node = dirs[dir] || (dirs[dir] = { isEntry: false }); }); node.isEntry = true; }); -exports.forEach = function(callback, context) { +exports.forEach = function (callback, context) { entryPoints.forEach(callback, context); }; @@ -76,9 +76,11 @@ exports.check = function (id, parentId) { return false; } - console.warn(`Risky cross-entry-point nested import of ${id} in ${ - partsAfterDist(parentId).join("/") - }`); + console.warn( + `Risky cross-entry-point nested import of ${id} in ${partsAfterDist( + parentId + ).join("/")}` + ); } } diff --git a/config/helpers.ts b/config/helpers.ts index e0e1b248ca1..58c306ae378 100644 --- a/config/helpers.ts +++ b/config/helpers.ts @@ -5,17 +5,17 @@ import glob = require("glob"); export const distDir = path.resolve(__dirname, "..", "dist"); -export function eachFile(dir: string, callback: ( - absPath: string, - relPath: string, -) => any) { +export function eachFile( + dir: string, + callback: (absPath: string, relPath: string) => any +) { const promises: Promise[] = []; return new Promise((resolve, reject) => { - glob(`${dir.replace(/\\/g, '/')}/**/*.js`, (error, files) => { + glob(`${dir.replace(/\\/g, "/")}/**/*.js`, (error, files) => { if (error) return reject(error); - files.sort().forEach(file => { + files.sort().forEach((file) => { const relPath = path.relative(dir, file); // Outside the distDir, somehow. @@ -32,9 +32,11 @@ export function eachFile(dir: string, callback: ( // This file is not meant to be imported or processed. if (relPath.endsWith("invariantErrorCodes.js")) return; - promises.push(new Promise(resolve => { - resolve(callback(file, relPath)); - })); + promises.push( + new Promise((resolve) => { + resolve(callback(file, relPath)); + }) + ); }); resolve(); diff --git a/config/jest.config.js b/config/jest.config.js index e8ae983c671..3dcd6e6de56 100644 --- a/config/jest.config.js +++ b/config/jest.config.js @@ -11,14 +11,14 @@ const defaults = { }, snapshotFormat: { escapeString: true, - printBasicPrototype: true + printBasicPrototype: true, }, transform: { - '^.+\\.tsx?$': [ - 'ts-jest', + "^.+\\.tsx?$": [ + "ts-jest", { diagnostics: { - warnOnly: process.env.TEST_ENV !== 'ci' + warnOnly: process.env.TEST_ENV !== "ci", }, }, ], @@ -26,22 +26,22 @@ const defaults = { resolver: "ts-jest-resolver", }; -const ignoreTSFiles = '.ts$'; -const ignoreTSXFiles = '.tsx$'; +const ignoreTSFiles = ".ts$"; +const ignoreTSXFiles = ".tsx$"; const react17TestFileIgnoreList = [ ignoreTSFiles, // For now, we only support useSuspenseQuery with React 18, so no need to test // it with React 17 - 'src/react/hooks/__tests__/useSuspenseQuery.test.tsx', - 'src/react/hooks/__tests__/useBackgroundQuery.test.tsx' -] + "src/react/hooks/__tests__/useSuspenseQuery.test.tsx", + "src/react/hooks/__tests__/useBackgroundQuery.test.tsx", +]; const tsStandardConfig = { ...defaults, - displayName: 'Core Tests', + displayName: "Core Tests", testPathIgnorePatterns: [ignoreTSXFiles], -} +}; // For both React (Jest) "projects", ignore core tests (.ts files) as they // do not import React, to avoid running them twice. @@ -65,9 +65,5 @@ const standardReact17Config = { }; module.exports = { - projects: [ - tsStandardConfig, - standardReact17Config, - standardReact18Config, - ], + projects: [tsStandardConfig, standardReact17Config, standardReact18Config], }; diff --git a/config/postprocessDist.ts b/config/postprocessDist.ts index 2d6d04d106e..6599c54ad0f 100644 --- a/config/postprocessDist.ts +++ b/config/postprocessDist.ts @@ -1,12 +1,14 @@ -import { distDir } from './helpers.ts'; -import fs from 'node:fs'; -import path from 'node:path'; +import { distDir } from "./helpers.ts"; +import fs from "node:fs"; +import path from "node:path"; const globalTypesFile = path.resolve(distDir, "utilities/globals/global.d.ts"); -fs.writeFileSync(globalTypesFile, - fs.readFileSync(globalTypesFile, "utf8") +fs.writeFileSync( + globalTypesFile, + fs + .readFileSync(globalTypesFile, "utf8") .split("\n") - .filter(line => line.trim() !== 'const __DEV__: boolean;') - .join("\n"), + .filter((line) => line.trim() !== "const __DEV__: boolean;") + .join("\n"), "utf8" -); \ No newline at end of file +); diff --git a/config/precheck.js b/config/precheck.js index 85de72c59b4..31548802c02 100644 --- a/config/precheck.js +++ b/config/precheck.js @@ -1,17 +1,10 @@ -const { - lockfileVersion, -} = require("../package-lock.json"); +const { lockfileVersion } = require("../package-lock.json"); const expectedVersion = 2; -if (typeof lockfileVersion !== "number" || - lockfileVersion < expectedVersion) { +if (typeof lockfileVersion !== "number" || lockfileVersion < expectedVersion) { throw new Error( - `Old lockfileVersion (${ - lockfileVersion - }) found in package-lock.json (expected ${ - expectedVersion - } or later)` + `Old lockfileVersion (${lockfileVersion}) found in package-lock.json (expected ${expectedVersion} or later)` ); } diff --git a/config/prepareDist.js b/config/prepareDist.js index 0205b0eb8bc..b0a4a1d2bd8 100644 --- a/config/prepareDist.js +++ b/config/prepareDist.js @@ -11,23 +11,22 @@ // - Create a new `package.json` for each sub-set bundle we support, and // store it in the appropriate dist sub-directory. -const fs = require('fs'); -const path = require('path'); -const recast = require('recast'); +const fs = require("fs"); +const path = require("path"); +const recast = require("recast"); const distRoot = `${__dirname}/../dist`; - /* @apollo/client */ -const packageJson = require('../package.json'); -const entryPoints = require('./entryPoints.js'); +const packageJson = require("../package.json"); +const entryPoints = require("./entryPoints.js"); // Enable default interpretation of .js files as ECMAScript modules. We don't // put this in the source ../package.json file because it interferes with tools // like ts-node, which we use to run various ../config/*.ts scripts. // TODO(benjamn) Fully diagnose that interference. -packageJson.type = 'module'; +packageJson.type = "module"; // The root package.json is marked as private to prevent publishing // from happening in the root of the project. This sets the package back to @@ -43,14 +42,19 @@ delete packageJson.engines; // on-going package development (e.g. running tests, supporting npm link, etc.). // When publishing from "dist" however, we need to update the package.json // to point to the files within the same directory. -const distPackageJson = JSON.stringify(packageJson, (_key, value) => { - if (typeof value === 'string' && value.startsWith('./dist/')) { - const parts = value.split('/'); - parts.splice(1, 1); // remove dist - return parts.join('/'); - } - return value; -}, 2) + "\n"; +const distPackageJson = + JSON.stringify( + packageJson, + (_key, value) => { + if (typeof value === "string" && value.startsWith("./dist/")) { + const parts = value.split("/"); + parts.splice(1, 1); // remove dist + return parts.join("/"); + } + return value; + }, + 2 + ) + "\n"; // Save the modified package.json to "dist" fs.writeFileSync(`${distRoot}/package.json`, distPackageJson); @@ -58,8 +62,8 @@ fs.writeFileSync(`${distRoot}/package.json`, distPackageJson); // Copy supporting files into "dist" const srcDir = `${__dirname}/..`; const destDir = `${srcDir}/dist`; -fs.copyFileSync(`${srcDir}/README.md`, `${destDir}/README.md`); -fs.copyFileSync(`${srcDir}/LICENSE`, `${destDir}/LICENSE`); +fs.copyFileSync(`${srcDir}/README.md`, `${destDir}/README.md`); +fs.copyFileSync(`${srcDir}/LICENSE`, `${destDir}/LICENSE`); // Create individual bundle package.json files, storing them in their // associated dist directory. This helps provide a way for the Apollo Client @@ -73,14 +77,18 @@ entryPoints.forEach(function buildPackageJson({ }) { if (!dirs.length) return; fs.writeFileSync( - path.join(distRoot, ...dirs, 'package.json'), - JSON.stringify({ - name: path.posix.join('@apollo', 'client', ...dirs), - type: "module", - main: `${bundleName}.cjs`, - module: 'index.js', - types: 'index.d.ts', - sideEffects, - }, null, 2) + "\n", + path.join(distRoot, ...dirs, "package.json"), + JSON.stringify( + { + name: path.posix.join("@apollo", "client", ...dirs), + type: "module", + main: `${bundleName}.cjs`, + module: "index.js", + types: "index.d.ts", + sideEffects, + }, + null, + 2 + ) + "\n" ); }); diff --git a/config/processInvariants.ts b/config/processInvariants.ts index 787772aa262..6534f36aa8a 100644 --- a/config/processInvariants.ts +++ b/config/processInvariants.ts @@ -1,24 +1,24 @@ -import * as fs from 'fs'; -import { posix, join as osPathJoin } from 'path'; -import { distDir, eachFile, reparse, reprint } from './helpers.ts'; -import type { ExpressionKind } from 'ast-types/lib/gen/kinds'; +import * as fs from "fs"; +import { posix, join as osPathJoin } from "path"; +import { distDir, eachFile, reparse, reprint } from "./helpers.ts"; +import type { ExpressionKind } from "ast-types/lib/gen/kinds"; eachFile(distDir, (file, relPath) => { - const source = fs.readFileSync(file, 'utf8'); + const source = fs.readFileSync(file, "utf8"); const output = transform(source, relPath); if (source !== output) { - fs.writeFileSync(file, output, 'utf8'); + fs.writeFileSync(file, output, "utf8"); } }).then(() => { fs.writeFileSync( - osPathJoin(distDir, 'invariantErrorCodes.js'), + osPathJoin(distDir, "invariantErrorCodes.js"), recast.print(program, { tabWidth: 2, - }).code + '\n' + }).code + "\n" ); }); -import * as recast from 'recast'; +import * as recast from "recast"; const b = recast.types.builders; const n = recast.types.namedTypes; type Node = recast.types.namedTypes.Node; @@ -28,28 +28,28 @@ let nextErrorCode = 1; const program = b.program([]); const allExports = { - errorCodes: getExportObject('errorCodes'), - devDebug: getExportObject('devDebug'), - devLog: getExportObject('devLog'), - devWarn: getExportObject('devWarn'), - devError: getExportObject('devError'), + errorCodes: getExportObject("errorCodes"), + devDebug: getExportObject("devDebug"), + devLog: getExportObject("devLog"), + devWarn: getExportObject("devWarn"), + devError: getExportObject("devError"), }; type ExportName = keyof typeof allExports; allExports.errorCodes.comments = [ b.commentLine( - ' This file is used by the error message display website and the', + " This file is used by the error message display website and the", true ), - b.commentLine(' @apollo/client/includeErrors entry point.', true), - b.commentLine(' This file is not meant to be imported manually.', true), + b.commentLine(" @apollo/client/includeErrors entry point.", true), + b.commentLine(" This file is not meant to be imported manually.", true), ]; function getExportObject(exportName: string) { const object = b.objectExpression([]); program.body.push( b.exportNamedDeclaration( - b.variableDeclaration('const', [ + b.variableDeclaration("const", [ b.variableDeclarator(b.identifier(exportName), object), ]) ) @@ -62,7 +62,7 @@ function getErrorCode( expr: CallExpression | NewExpression, type: keyof typeof allExports ): ExpressionKind { - if (isIdWithName(expr.callee, 'invariant')) { + if (isIdWithName(expr.callee, "invariant")) { return extractString( file, allExports[type].properties, @@ -75,44 +75,43 @@ function getErrorCode( function extractString( file: string, - target: typeof allExports[ExportName]['properties'], + target: (typeof allExports)[ExportName]["properties"], message: recast.types.namedTypes.SpreadElement | ExpressionKind, condition?: recast.types.namedTypes.SpreadElement | ExpressionKind ): ExpressionKind { - if (message.type === 'ConditionalExpression') { + if (message.type === "ConditionalExpression") { return b.conditionalExpression( message.test, extractString(file, target, message.consequent, condition), extractString(file, target, message.alternate, condition) ); } else if (isStringOnly(message)) { - const messageText = reprint(message); - if (messageText.includes('Apollo DevTools')) { + if (messageText.includes("Apollo DevTools")) { return message; } const obj = b.objectExpression([]); const numLit = b.numericLiteral(nextErrorCode++); - target.push(b.property('init', numLit, obj)); + target.push(b.property("init", numLit, obj)); obj.properties.push( b.property( - 'init', - b.identifier('file'), - b.stringLiteral('@apollo/client/' + file) + "init", + b.identifier("file"), + b.stringLiteral("@apollo/client/" + file) ) ); if (condition) { obj.properties.push( b.property( - 'init', - b.identifier('condition'), + "init", + b.identifier("condition"), b.stringLiteral(reprint(expr.arguments[0])) ) ); } - obj.properties.push(b.property('init', b.identifier('message'), message)); + obj.properties.push(b.property("init", b.identifier("message"), message)); return numLit; } else { @@ -134,12 +133,12 @@ function transform(code: string, relativeFilePath: string) { this.traverse(path); const node = path.node; - if (isCallWithLength(node, 'invariant', 1)) { + if (isCallWithLength(node, "invariant", 1)) { const newArgs = [...node.arguments]; newArgs.splice( 1, 1, - getErrorCode(relativeFilePath, node, 'errorCodes') + getErrorCode(relativeFilePath, node, "errorCodes") ); return b.callExpression.from({ @@ -148,12 +147,12 @@ function transform(code: string, relativeFilePath: string) { }); } - if (isCallWithLength(node, 'newInvariantError', 0)) { + if (isCallWithLength(node, "newInvariantError", 0)) { const newArgs = [...node.arguments]; newArgs.splice( 0, 1, - getErrorCode(relativeFilePath, node, 'errorCodes') + getErrorCode(relativeFilePath, node, "errorCodes") ); return b.callExpression.from({ @@ -163,14 +162,14 @@ function transform(code: string, relativeFilePath: string) { } if ( - node.callee.type === 'MemberExpression' && - isIdWithName(node.callee.object, 'invariant') && - isIdWithName(node.callee.property, 'debug', 'log', 'warn', 'error') + node.callee.type === "MemberExpression" && + isIdWithName(node.callee.object, "invariant") && + isIdWithName(node.callee.property, "debug", "log", "warn", "error") ) { let newNode = node; - if (node.arguments[0].type !== 'Identifier') { + if (node.arguments[0].type !== "Identifier") { const prop = node.callee.property; - if (!n.Identifier.check(prop)) throw new Error('unexpected type'); + if (!n.Identifier.check(prop)) throw new Error("unexpected type"); const newArgs = [...node.arguments]; newArgs.splice( @@ -179,7 +178,7 @@ function transform(code: string, relativeFilePath: string) { getErrorCode( relativeFilePath, node, - ('dev' + capitalize(prop.name)) as ExportName + ("dev" + capitalize(prop.name)) as ExportName ) ); newNode = b.callExpression.from({ @@ -191,22 +190,26 @@ function transform(code: string, relativeFilePath: string) { if (isDEVLogicalAnd(path.parent.node)) { return newNode; } - return b.logicalExpression('&&', makeDEVExpr(), newNode); + return b.logicalExpression("&&", makeDEVExpr(), newNode); } }, }); - if (!['utilities/globals/index.js', 'config/jest/setup.js'].includes(relativeFilePath)) + if ( + !["utilities/globals/index.js", "config/jest/setup.js"].includes( + relativeFilePath + ) + ) recast.visit(ast, { visitIdentifier(path) { this.traverse(path); const node = path.node; if (isDEVExpr(node)) { return b.binaryExpression( - '!==', + "!==", b.memberExpression( - b.identifier('globalThis'), - b.identifier('__DEV__') + b.identifier("globalThis"), + b.identifier("__DEV__") ), b.literal(false) ); @@ -235,33 +238,33 @@ function isCallWithLength( function isDEVLogicalAnd(node: Node) { return ( n.LogicalExpression.check(node) && - node.operator === '&&' && + node.operator === "&&" && isDEVExpr(node.left) ); } function makeDEVExpr() { - return b.identifier('__DEV__'); + return b.identifier("__DEV__"); } function isDEVExpr(node: Node) { - return isIdWithName(node, '__DEV__'); + return isIdWithName(node, "__DEV__"); } function isStringOnly( node: recast.types.namedTypes.ASTNode ): node is ExpressionKind { switch (node.type) { - case 'StringLiteral': - case 'Literal': + case "StringLiteral": + case "Literal": return true; - case 'TemplateLiteral': + case "TemplateLiteral": return (node.expressions as recast.types.namedTypes.ASTNode[]).every( isStringOnly ); - case 'BinaryExpression': + case "BinaryExpression": return ( - node.operator == '+' && + node.operator == "+" && isStringOnly(node.left) && isStringOnly(node.right) ); diff --git a/config/rewriteSourceMaps.ts b/config/rewriteSourceMaps.ts index cb3c284ef0d..7aafafd8065 100644 --- a/config/rewriteSourceMaps.ts +++ b/config/rewriteSourceMaps.ts @@ -1,9 +1,9 @@ import * as fs from "fs"; import * as path from "path"; -import { distDir } from './helpers.ts'; +import { distDir } from "./helpers.ts"; import glob = require("glob"); -glob(`${distDir.replace(/\\/g, '/')}/**/*.js.map`, (error, files) => { +glob(`${distDir.replace(/\\/g, "/")}/**/*.js.map`, (error, files) => { if (error) throw error; const rootDir = path.dirname(distDir); @@ -11,34 +11,38 @@ glob(`${distDir.replace(/\\/g, '/')}/**/*.js.map`, (error, files) => { const startTime = Date.now(); let rewriteCount = 0; - Promise.all(files.map(async file => { - const content = await fs.promises.readFile(file, "utf8"); - const map = JSON.parse(content); - if (map.sourcesContent) return; - if (map.sources) { - map.sourcesContent = await Promise.all( - map.sources.map((relSourcePath: string) => { - const sourcePath = path.normalize( - path.join(path.dirname(file), relSourcePath)); - const relPath = path.relative(rootDir, sourcePath); - // Disallow reading paths outside rootDir. - if (relPath.startsWith("../")) { - throw new Error(`Bad path: ${sourcePath}`); - } - return fs.promises.readFile(sourcePath, "utf8"); - }) + Promise.all( + files.map(async (file) => { + const content = await fs.promises.readFile(file, "utf8"); + const map = JSON.parse(content); + if (map.sourcesContent) return; + if (map.sources) { + map.sourcesContent = await Promise.all( + map.sources.map((relSourcePath: string) => { + const sourcePath = path.normalize( + path.join(path.dirname(file), relSourcePath) + ); + const relPath = path.relative(rootDir, sourcePath); + // Disallow reading paths outside rootDir. + if (relPath.startsWith("../")) { + throw new Error(`Bad path: ${sourcePath}`); + } + return fs.promises.readFile(sourcePath, "utf8"); + }) + ); + ++rewriteCount; + return fs.promises.writeFile(file, JSON.stringify(map)); + } + }) + ).then( + () => { + console.log( + `Rewrote ${rewriteCount} source maps in ${Date.now() - startTime}ms` ); - ++rewriteCount; - return fs.promises.writeFile(file, JSON.stringify(map)); + }, + (error) => { + console.error(error); + process.exit(-1); } - })).then(() => { - console.log(`Rewrote ${ - rewriteCount - } source maps in ${ - Date.now() - startTime - }ms`); - }, error => { - console.error(error); - process.exit(-1); - }); + ); }); diff --git a/config/rollup.config.js b/config/rollup.config.js index 081c430d278..7917a340f56 100644 --- a/config/rollup.config.js +++ b/config/rollup.config.js @@ -1,38 +1,32 @@ -import path, { resolve, dirname } from 'path'; +import path, { resolve, dirname } from "path"; import { promises as fs } from "fs"; -import nodeResolve from '@rollup/plugin-node-resolve'; -import { terser as minify } from 'rollup-plugin-terser'; +import nodeResolve from "@rollup/plugin-node-resolve"; +import { terser as minify } from "rollup-plugin-terser"; -const entryPoints = require('./entryPoints'); -const distDir = './dist'; +const entryPoints = require("./entryPoints"); +const distDir = "./dist"; function isExternal(id, parentId, entryPointsAreExternal = true) { - let posixId = toPosixPath(id) + let posixId = toPosixPath(id); const posixParentId = toPosixPath(parentId); // Rollup v2.26.8 started passing absolute id strings to this function, thanks // apparently to https://github.com/rollup/rollup/pull/3753, so we relativize // the id again in those cases. if (path.isAbsolute(id)) { - posixId = path.posix.relative( - path.posix.dirname(posixParentId), - posixId, - ); + posixId = path.posix.relative(path.posix.dirname(posixParentId), posixId); if (!posixId.startsWith(".")) { posixId = "./" + posixId; } } - const isRelative = - posixId.startsWith("./") || - posixId.startsWith("../"); + const isRelative = posixId.startsWith("./") || posixId.startsWith("../"); if (!isRelative) { return true; } - if (entryPointsAreExternal && - entryPoints.check(posixId, posixParentId)) { + if (entryPointsAreExternal && entryPoints.check(posixId, posixParentId)) { return true; } @@ -43,14 +37,14 @@ function isExternal(id, parentId, entryPointsAreExternal = true) { function toPosixPath(p) { // Sometimes, you can have a path like \Users\IEUser on windows, and this // actually means you want C:\Users\IEUser - if (p[0] === '\\') { + if (p[0] === "\\") { p = process.env.SystemDrive + p; } - p = p.replace(/\\/g, '/'); - if (p[1] === ':') { + p = p.replace(/\\/g, "/"); + if (p[1] === ":") { // Transform "C:/bla/bla" to "/c/bla/bla" - p = '/' + p[0] + p.slice(2); + p = "/" + p[0] + p.slice(2); } return p; @@ -64,14 +58,12 @@ function prepareCJS(input, output) { }, output: { file: output, - format: 'cjs', + format: "cjs", sourcemap: true, - exports: 'named', + exports: "named", externalLiveBindings: false, }, - plugins: [ - nodeResolve(), - ], + plugins: [nodeResolve()], }; } @@ -79,8 +71,8 @@ function prepareCJSMinified(input) { return { input, output: { - file: input.replace('.cjs', '.min.cjs'), - format: 'cjs', + file: input.replace(".cjs", ".min.cjs"), + format: "cjs", }, plugins: [ minify({ @@ -90,7 +82,7 @@ function prepareCJSMinified(input) { compress: { toplevel: true, global_defs: { - '@globalThis.__DEV__': 'false', + "@globalThis.__DEV__": "false", }, }, }), @@ -113,44 +105,44 @@ function prepareBundle({ // external(id, parentId) {} output: { file: outputFile, - format: 'cjs', + format: "cjs", sourcemap: true, - exports: 'named', + exports: "named", externalLiveBindings: false, }, plugins: [ { - name: 'externalize-dependency', + name: "externalize-dependency", resolveId(id, parentId) { if (!parentId) { return null; } function removeIndex(filename) { if (filename.endsWith(`${path.sep}index.js`)) { - return filename.slice(0, -`${path.sep}index.js`.length) + return filename.slice(0, -`${path.sep}index.js`.length); } - return filename + return filename; } - const external = isExternal(id, parentId, true) + const external = isExternal(id, parentId, true); if (external) { if (id.startsWith(".")) { - return { id: removeIndex(resolve(dirname(parentId), id)), external: true }; + return { + id: removeIndex(resolve(dirname(parentId), id)), + external: true, + }; } return { id: removeIndex(id), external: true }; } return null; - } + }, }, extensions ? nodeResolve({ extensions }) : nodeResolve(), { name: "copy *.cjs to *.cjs.native.js", async writeBundle({ file }) { const buffer = await fs.readFile(file); - await fs.writeFile( - file + ".native.js", - buffer, - ); + await fs.writeFile(file + ".native.js", buffer); }, }, ], @@ -160,11 +152,6 @@ function prepareBundle({ export default [ ...entryPoints.map(prepareBundle), // Convert the ESM entry point to a single CJS bundle. - prepareCJS( - './dist/index.js', - './dist/apollo-client.cjs', - ), - prepareCJSMinified( - './dist/apollo-client.cjs', - ), + prepareCJS("./dist/index.js", "./dist/apollo-client.cjs"), + prepareCJSMinified("./dist/apollo-client.cjs"), ]; diff --git a/config/tsconfig.json b/config/tsconfig.json index d8975964558..39e1fe94587 100644 --- a/config/tsconfig.json +++ b/config/tsconfig.json @@ -20,6 +20,6 @@ // respect this option unless the source maps are inlined into the // source files, which we definitely do not want. Instead, we use the // config/rewriteSourceMaps.ts script to add sourcesContent. - "inlineSources": true, - }, + "inlineSources": true + } } diff --git a/config/version.js b/config/version.js index 0abf54fe5c9..de66893159c 100644 --- a/config/version.js +++ b/config/version.js @@ -7,8 +7,9 @@ const pkgJsonPath = path.join(__dirname, "..", "package.json"); const { version } = JSON.parse(fs.readFileSync(pkgJsonPath)); assert.strictEqual( - typeof version, "string", - '"version" field missing from package.json', + typeof version, + "string", + '"version" field missing from package.json' ); switch (process.argv[2]) { @@ -18,8 +19,9 @@ switch (process.argv[2]) { .replace(/\blocal\b/, version); assert.notEqual( - updated.indexOf(version), -1, - "Failed to update dist/version.js with @apollo/client version", + updated.indexOf(version), + -1, + "Failed to update dist/version.js with @apollo/client version" ); fs.writeFileSync(versionPath, updated); @@ -28,15 +30,16 @@ switch (process.argv[2]) { } case "verify": { - const { - ApolloClient, - InMemoryCache, - } = require(path.join(distRoot, "core", "core.cjs")); + const { ApolloClient, InMemoryCache } = require(path.join( + distRoot, + "core", + "core.cjs" + )); // Though this may seem like overkill, verifying that ApolloClient is // constructible in Node.js is actually pretty useful, too! const client = new ApolloClient({ - cache: new InMemoryCache, + cache: new InMemoryCache(), }); // Probably not necessary, but it seems wise to clean up any resources @@ -50,17 +53,16 @@ switch (process.argv[2]) { // convenient because dist/version.js uses ECMAScript module syntax, and is // thus not importable in all versions of Node.js. assert.strictEqual( - client.version, version, - "Failed to update dist/version.js and dist/core/core.cjs", + client.version, + version, + "Failed to update dist/version.js and dist/core/core.cjs" ); break; } default: - throw new Error( - "Pass either 'update' or 'verify' to config/version.js" - ); + throw new Error("Pass either 'update' or 'verify' to config/version.js"); } console.log("ok"); diff --git a/integration-tests/browser-esm/html/jsdeliver-esm.html b/integration-tests/browser-esm/html/jsdeliver-esm.html index 18a50afacc2..8d0109854d0 100644 --- a/integration-tests/browser-esm/html/jsdeliver-esm.html +++ b/integration-tests/browser-esm/html/jsdeliver-esm.html @@ -1,43 +1,47 @@ - + + + - - -

https://cdn.jsdelivr.net/npm/@apollo/client/+esm

-
-

loading

-
- + .then((r) => { + const main = document.querySelector("main"); + const ul = document.createElement("ul"); + main.replaceChildren(ul); + r.data.products.forEach((p) => { + const li = document.createElement("li"); + li.textContent = p.title; + ul.appendChild(li); + }); + }) + .catch(console.error); + + +

https://cdn.jsdelivr.net/npm/@apollo/client/+esm

+
+

loading

+
+ diff --git a/integration-tests/browser-esm/html/jspm-prepared.html b/integration-tests/browser-esm/html/jspm-prepared.html index aad2bbbd48a..7395ff614af 100644 --- a/integration-tests/browser-esm/html/jspm-prepared.html +++ b/integration-tests/browser-esm/html/jspm-prepared.html @@ -1,66 +1,66 @@ - + - - - - + - - -

https://ga.jspm.io/npm:@apollo/client/index.js

-
-

loading

-
- + .then((r) => { + const main = document.querySelector("main"); + const ul = document.createElement("ul"); + main.replaceChildren(ul); + r.data.products.forEach((p) => { + const li = document.createElement("li"); + li.textContent = p.title; + ul.appendChild(li); + }); + }) + .catch(console.error); + + +

https://ga.jspm.io/npm:@apollo/client/index.js

+
+

loading

+
+ diff --git a/integration-tests/browser-esm/html/unpkg-unmangled.html b/integration-tests/browser-esm/html/unpkg-unmangled.html index 526eab19b07..0e7de49a86b 100644 --- a/integration-tests/browser-esm/html/unpkg-unmangled.html +++ b/integration-tests/browser-esm/html/unpkg-unmangled.html @@ -1,62 +1,62 @@ - + - - - - + + + +

https://unpkg.com/@apollo/client/index.js

+
+

loading

+
+ diff --git a/integration-tests/browser-esm/playwright.config.ts b/integration-tests/browser-esm/playwright.config.ts index 2dba68c3fe8..87e14a4b00c 100644 --- a/integration-tests/browser-esm/playwright.config.ts +++ b/integration-tests/browser-esm/playwright.config.ts @@ -1,4 +1,4 @@ -import { baseConfig } from 'shared/playwright.config'; -import { defineConfig } from '@playwright/test'; +import { baseConfig } from "shared/playwright.config"; +import { defineConfig } from "@playwright/test"; export default defineConfig(baseConfig); diff --git a/integration-tests/browser-esm/tests/playwright/jsdeliver-esm.test.ts b/integration-tests/browser-esm/tests/playwright/jsdeliver-esm.test.ts index acefc15a6dd..fc382ebfc7a 100644 --- a/integration-tests/browser-esm/tests/playwright/jsdeliver-esm.test.ts +++ b/integration-tests/browser-esm/tests/playwright/jsdeliver-esm.test.ts @@ -1,10 +1,10 @@ -import { expect } from '@playwright/test'; -import { test } from 'shared/fixture'; +import { expect } from "@playwright/test"; +import { test } from "shared/fixture"; -test('Basic Test', async ({ page, withHar }) => { - await page.goto('http://localhost:3000/jsdeliver-esm.html'); +test("Basic Test", async ({ page, withHar }) => { + await page.goto("http://localhost:3000/jsdeliver-esm.html"); - await expect(page.getByText('loading')).toBeVisible(); - await expect(page.getByText('loading')).not.toBeVisible({ timeout: 10000 }); - await expect(page.getByText('Soft Warm Apollo Beanie')).toBeVisible(); + await expect(page.getByText("loading")).toBeVisible(); + await expect(page.getByText("loading")).not.toBeVisible({ timeout: 10000 }); + await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); }); diff --git a/integration-tests/browser-esm/tests/playwright/jspm-prepared.test.ts b/integration-tests/browser-esm/tests/playwright/jspm-prepared.test.ts index ec273be797f..6d18d39e864 100644 --- a/integration-tests/browser-esm/tests/playwright/jspm-prepared.test.ts +++ b/integration-tests/browser-esm/tests/playwright/jspm-prepared.test.ts @@ -1,10 +1,10 @@ -import { expect } from '@playwright/test'; -import { test } from 'shared/fixture'; +import { expect } from "@playwright/test"; +import { test } from "shared/fixture"; -test('Basic Test', async ({ page, withHar }) => { - await page.goto('http://localhost:3000/jspm-prepared.html'); +test("Basic Test", async ({ page, withHar }) => { + await page.goto("http://localhost:3000/jspm-prepared.html"); - await expect(page.getByText('loading')).toBeVisible(); - await expect(page.getByText('loading')).not.toBeVisible({ timeout: 10000 }); - await expect(page.getByText('Soft Warm Apollo Beanie')).toBeVisible(); + await expect(page.getByText("loading")).toBeVisible(); + await expect(page.getByText("loading")).not.toBeVisible({ timeout: 10000 }); + await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); }); diff --git a/integration-tests/browser-esm/tests/playwright/unpkg-unmangled.test.ts b/integration-tests/browser-esm/tests/playwright/unpkg-unmangled.test.ts index ab495d74812..0ab77f15c66 100644 --- a/integration-tests/browser-esm/tests/playwright/unpkg-unmangled.test.ts +++ b/integration-tests/browser-esm/tests/playwright/unpkg-unmangled.test.ts @@ -1,12 +1,12 @@ -import { expect } from '@playwright/test'; -import { test } from 'shared/fixture'; +import { expect } from "@playwright/test"; +import { test } from "shared/fixture"; -test('Basic Test', async ({ withHar }) => { - await withHar.goto('http://localhost:3000/unpkg-unmangled.html'); +test("Basic Test", async ({ withHar }) => { + await withHar.goto("http://localhost:3000/unpkg-unmangled.html"); - await expect(withHar.getByText('loading')).toBeVisible(); - await expect(withHar.getByText('loading')).not.toBeVisible({ + await expect(withHar.getByText("loading")).toBeVisible(); + await expect(withHar.getByText("loading")).not.toBeVisible({ timeout: 10000, }); - await expect(withHar.getByText('Soft Warm Apollo Beanie')).toBeVisible(); + await expect(withHar.getByText("Soft Warm Apollo Beanie")).toBeVisible(); }); diff --git a/integration-tests/cra4/playwright.config.ts b/integration-tests/cra4/playwright.config.ts index 2dba68c3fe8..87e14a4b00c 100644 --- a/integration-tests/cra4/playwright.config.ts +++ b/integration-tests/cra4/playwright.config.ts @@ -1,4 +1,4 @@ -import { baseConfig } from 'shared/playwright.config'; -import { defineConfig } from '@playwright/test'; +import { baseConfig } from "shared/playwright.config"; +import { defineConfig } from "@playwright/test"; export default defineConfig(baseConfig); diff --git a/integration-tests/cra4/public/index.html b/integration-tests/cra4/public/index.html index 79a9558d087..6a5b8179788 100644 --- a/integration-tests/cra4/public/index.html +++ b/integration-tests/cra4/public/index.html @@ -1,17 +1,19 @@ - + - - - - - - - - - - Test App - + Test App + - - -
- - - + diff --git a/integration-tests/cra4/src/App.tsx b/integration-tests/cra4/src/App.tsx index e411245729a..cf87849afbd 100644 --- a/integration-tests/cra4/src/App.tsx +++ b/integration-tests/cra4/src/App.tsx @@ -1,4 +1,4 @@ -import type { TypedDocumentNode } from '@apollo/client'; +import type { TypedDocumentNode } from "@apollo/client"; import { useQuery, @@ -9,7 +9,7 @@ import { ApolloLink, Observable, HttpLink, -} from '@apollo/client'; +} from "@apollo/client"; const delayLink = new ApolloLink((operation, forward) => { return new Observable((observer) => { @@ -22,7 +22,7 @@ const delayLink = new ApolloLink((operation, forward) => { }); const httpLink = new HttpLink({ - uri: 'https://main--hack-the-e-commerce.apollographos.net/graphql', + uri: "https://main--hack-the-e-commerce.apollographos.net/graphql", }); const client = new ApolloClient({ @@ -56,11 +56,7 @@ function Main() { const { data } = useQuery(QUERY); return data ? ( -
    - {data?.products.map(({ id, title }) => ( -
  • {title}
  • - ))} -
+
    {data?.products.map(({ id, title }) =>
  • {title}
  • )}
) : ( <>loading ); diff --git a/integration-tests/cra4/src/index.tsx b/integration-tests/cra4/src/index.tsx index d8c4aaa3581..e73156e93e3 100644 --- a/integration-tests/cra4/src/index.tsx +++ b/integration-tests/cra4/src/index.tsx @@ -1,11 +1,11 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import App from './App'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; // Initialize the msw worker, wait for the service worker registration to resolve, then mount async function render() { const rootNode = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement + document.getElementById("root") as HTMLElement ); rootNode.render( diff --git a/integration-tests/cra4/tests/playwright/apollo-client.test.ts b/integration-tests/cra4/tests/playwright/apollo-client.test.ts index 56be036d4f5..a37db778140 100644 --- a/integration-tests/cra4/tests/playwright/apollo-client.test.ts +++ b/integration-tests/cra4/tests/playwright/apollo-client.test.ts @@ -1,10 +1,10 @@ -import { expect } from '@playwright/test'; -import { test } from 'shared/fixture'; +import { expect } from "@playwright/test"; +import { test } from "shared/fixture"; -test('Basic Test', async ({ page, withHar }) => { - await page.goto('http://localhost:3000'); +test("Basic Test", async ({ page, withHar }) => { + await page.goto("http://localhost:3000"); - await expect(page.getByText('loading')).toBeVisible(); - await expect(page.getByText('loading')).not.toBeVisible(); - await expect(page.getByText('Soft Warm Apollo Beanie')).toBeVisible(); + await expect(page.getByText("loading")).toBeVisible(); + await expect(page.getByText("loading")).not.toBeVisible(); + await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); }); diff --git a/integration-tests/cra4/tsconfig.json b/integration-tests/cra4/tsconfig.json index a273b0cfc0e..9d379a3c4af 100644 --- a/integration-tests/cra4/tsconfig.json +++ b/integration-tests/cra4/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -20,7 +16,5 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": [ - "src" - ] + "include": ["src"] } diff --git a/integration-tests/cra5/playwright.config.ts b/integration-tests/cra5/playwright.config.ts index 2dba68c3fe8..87e14a4b00c 100644 --- a/integration-tests/cra5/playwright.config.ts +++ b/integration-tests/cra5/playwright.config.ts @@ -1,4 +1,4 @@ -import { baseConfig } from 'shared/playwright.config'; -import { defineConfig } from '@playwright/test'; +import { baseConfig } from "shared/playwright.config"; +import { defineConfig } from "@playwright/test"; export default defineConfig(baseConfig); diff --git a/integration-tests/cra5/public/index.html b/integration-tests/cra5/public/index.html index 2aed6ed2183..90660254517 100644 --- a/integration-tests/cra5/public/index.html +++ b/integration-tests/cra5/public/index.html @@ -1,12 +1,14 @@ - + - - - - - - - - React App - + React App + - - -
- - - + diff --git a/integration-tests/cra5/src/App.tsx b/integration-tests/cra5/src/App.tsx index e411245729a..cf87849afbd 100644 --- a/integration-tests/cra5/src/App.tsx +++ b/integration-tests/cra5/src/App.tsx @@ -1,4 +1,4 @@ -import type { TypedDocumentNode } from '@apollo/client'; +import type { TypedDocumentNode } from "@apollo/client"; import { useQuery, @@ -9,7 +9,7 @@ import { ApolloLink, Observable, HttpLink, -} from '@apollo/client'; +} from "@apollo/client"; const delayLink = new ApolloLink((operation, forward) => { return new Observable((observer) => { @@ -22,7 +22,7 @@ const delayLink = new ApolloLink((operation, forward) => { }); const httpLink = new HttpLink({ - uri: 'https://main--hack-the-e-commerce.apollographos.net/graphql', + uri: "https://main--hack-the-e-commerce.apollographos.net/graphql", }); const client = new ApolloClient({ @@ -56,11 +56,7 @@ function Main() { const { data } = useQuery(QUERY); return data ? ( -
    - {data?.products.map(({ id, title }) => ( -
  • {title}
  • - ))} -
+
    {data?.products.map(({ id, title }) =>
  • {title}
  • )}
) : ( <>loading ); diff --git a/integration-tests/cra5/src/index.tsx b/integration-tests/cra5/src/index.tsx index 88e8a31a441..153cce06c5a 100644 --- a/integration-tests/cra5/src/index.tsx +++ b/integration-tests/cra5/src/index.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import App from './App'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; const root = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement + document.getElementById("root") as HTMLElement ); root.render( diff --git a/integration-tests/cra5/tests/playwright/apollo-client.test.ts b/integration-tests/cra5/tests/playwright/apollo-client.test.ts index 56be036d4f5..a37db778140 100644 --- a/integration-tests/cra5/tests/playwright/apollo-client.test.ts +++ b/integration-tests/cra5/tests/playwright/apollo-client.test.ts @@ -1,10 +1,10 @@ -import { expect } from '@playwright/test'; -import { test } from 'shared/fixture'; +import { expect } from "@playwright/test"; +import { test } from "shared/fixture"; -test('Basic Test', async ({ page, withHar }) => { - await page.goto('http://localhost:3000'); +test("Basic Test", async ({ page, withHar }) => { + await page.goto("http://localhost:3000"); - await expect(page.getByText('loading')).toBeVisible(); - await expect(page.getByText('loading')).not.toBeVisible(); - await expect(page.getByText('Soft Warm Apollo Beanie')).toBeVisible(); + await expect(page.getByText("loading")).toBeVisible(); + await expect(page.getByText("loading")).not.toBeVisible(); + await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); }); diff --git a/integration-tests/cra5/tsconfig.json b/integration-tests/cra5/tsconfig.json index a273b0cfc0e..9d379a3c4af 100644 --- a/integration-tests/cra5/tsconfig.json +++ b/integration-tests/cra5/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -20,7 +16,5 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": [ - "src" - ] + "include": ["src"] } diff --git a/integration-tests/next/next.config.js b/integration-tests/next/next.config.js index 767719fc4fb..658404ac690 100644 --- a/integration-tests/next/next.config.js +++ b/integration-tests/next/next.config.js @@ -1,4 +1,4 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = {}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/integration-tests/next/playwright.config.ts b/integration-tests/next/playwright.config.ts index 2dba68c3fe8..87e14a4b00c 100644 --- a/integration-tests/next/playwright.config.ts +++ b/integration-tests/next/playwright.config.ts @@ -1,4 +1,4 @@ -import { baseConfig } from 'shared/playwright.config'; -import { defineConfig } from '@playwright/test'; +import { baseConfig } from "shared/playwright.config"; +import { defineConfig } from "@playwright/test"; export default defineConfig(baseConfig); diff --git a/integration-tests/next/src/app/cc/ApolloWrapper.tsx b/integration-tests/next/src/app/cc/ApolloWrapper.tsx index 3050c2a2e40..f5430c2371d 100644 --- a/integration-tests/next/src/app/cc/ApolloWrapper.tsx +++ b/integration-tests/next/src/app/cc/ApolloWrapper.tsx @@ -1,34 +1,32 @@ -'use client'; -import React from 'react'; -import { HttpLink } from '@apollo/client'; +"use client"; +import React from "react"; +import { HttpLink } from "@apollo/client"; import { ApolloNextAppProvider, NextSSRInMemoryCache, NextSSRApolloClient, -} from '@apollo/experimental-nextjs-app-support/ssr'; +} from "@apollo/experimental-nextjs-app-support/ssr"; -import { loadErrorMessages, loadDevMessages } from '@apollo/client/dev'; -import { setVerbosity } from 'ts-invariant'; -import { schemaLink } from '@/libs/schemaLink'; +import { loadErrorMessages, loadDevMessages } from "@apollo/client/dev"; +import { setVerbosity } from "ts-invariant"; +import { schemaLink } from "@/libs/schemaLink"; //if (process.env.NODE_ENV === 'development') { -setVerbosity('debug'); +setVerbosity("debug"); loadDevMessages(); loadErrorMessages(); //} export function ApolloWrapper({ children }: React.PropsWithChildren<{}>) { return ( - + {children} ); function makeClient() { const httpLink = new HttpLink({ - uri: 'https://main--hack-the-e-commerce.apollographos.net/graphql', + uri: "https://main--hack-the-e-commerce.apollographos.net/graphql", }); return new NextSSRApolloClient({ diff --git a/integration-tests/next/src/app/cc/layout.tsx b/integration-tests/next/src/app/cc/layout.tsx index e13903f16e2..bc8642b3a3d 100644 --- a/integration-tests/next/src/app/cc/layout.tsx +++ b/integration-tests/next/src/app/cc/layout.tsx @@ -1,4 +1,4 @@ -import { ApolloWrapper } from './ApolloWrapper'; +import { ApolloWrapper } from "./ApolloWrapper"; export default async function Layout({ children, diff --git a/integration-tests/next/src/app/cc/page.tsx b/integration-tests/next/src/app/cc/page.tsx index 29b19b11bc5..8977c8bfbe3 100644 --- a/integration-tests/next/src/app/cc/page.tsx +++ b/integration-tests/next/src/app/cc/page.tsx @@ -1,8 +1,8 @@ -'use client'; +"use client"; -import { useSuspenseQuery } from '@apollo/experimental-nextjs-app-support/ssr'; -import type { TypedDocumentNode } from '@apollo/client'; -import { gql } from '@apollo/client'; +import { useSuspenseQuery } from "@apollo/experimental-nextjs-app-support/ssr"; +import type { TypedDocumentNode } from "@apollo/client"; +import { gql } from "@apollo/client"; const QUERY: TypedDocumentNode<{ products: { diff --git a/integration-tests/next/src/app/client.ts b/integration-tests/next/src/app/client.ts index 844a5b97930..b43496b26de 100644 --- a/integration-tests/next/src/app/client.ts +++ b/integration-tests/next/src/app/client.ts @@ -1,6 +1,6 @@ -import { schemaLink } from '@/libs/schemaLink'; -import { ApolloClient, InMemoryCache } from '@apollo/client'; -import { registerApolloClient } from '@apollo/experimental-nextjs-app-support/rsc'; +import { schemaLink } from "@/libs/schemaLink"; +import { ApolloClient, InMemoryCache } from "@apollo/client"; +import { registerApolloClient } from "@apollo/experimental-nextjs-app-support/rsc"; export const { getClient } = registerApolloClient(() => { return new ApolloClient({ diff --git a/integration-tests/next/src/app/layout.tsx b/integration-tests/next/src/app/layout.tsx index e77f1373af7..7e3b1753e2a 100644 --- a/integration-tests/next/src/app/layout.tsx +++ b/integration-tests/next/src/app/layout.tsx @@ -1,6 +1,6 @@ export const metadata = { - title: 'Create Next App', - description: 'Generated by create next app', + title: "Create Next App", + description: "Generated by create next app", }; export default function RootLayout({ diff --git a/integration-tests/next/src/app/page.tsx b/integration-tests/next/src/app/page.tsx index c1a10df84c7..96bd69a7efa 100644 --- a/integration-tests/next/src/app/page.tsx +++ b/integration-tests/next/src/app/page.tsx @@ -1,6 +1,6 @@ -import type { TypedDocumentNode } from '@apollo/client'; -import { gql } from '@apollo/client'; -import { getClient } from './client'; +import type { TypedDocumentNode } from "@apollo/client"; +import { gql } from "@apollo/client"; +import { getClient } from "./client"; const QUERY: TypedDocumentNode<{ products: { diff --git a/integration-tests/next/src/libs/apolloClient.ts b/integration-tests/next/src/libs/apolloClient.ts index ec92057546c..96a17171142 100644 --- a/integration-tests/next/src/libs/apolloClient.ts +++ b/integration-tests/next/src/libs/apolloClient.ts @@ -1,5 +1,5 @@ -import { useRef } from 'react'; -import type { NormalizedCacheObject } from '@apollo/client'; +import { useRef } from "react"; +import type { NormalizedCacheObject } from "@apollo/client"; import { ApolloClient, HttpLink, @@ -7,14 +7,14 @@ import { from, ApolloLink, Observable, -} from '@apollo/client'; -import { onError } from '@apollo/client/link/error'; -import merge from 'deepmerge'; -import isEqual from 'lodash/isEqual'; -import type { GetServerSidePropsResult } from 'next'; -import { schemaLink } from './schemaLink'; +} from "@apollo/client"; +import { onError } from "@apollo/client/link/error"; +import merge from "deepmerge"; +import isEqual from "lodash/isEqual"; +import type { GetServerSidePropsResult } from "next"; +import { schemaLink } from "./schemaLink"; -export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__'; +export const APOLLO_STATE_PROP_NAME = "__APOLLO_STATE__"; let apolloClient: ApolloClient; @@ -39,13 +39,17 @@ const delayLink = new ApolloLink((operation, forward) => { }); const httpLink = new HttpLink({ - uri: 'https://main--hack-the-e-commerce.apollographos.net/graphql', + uri: "https://main--hack-the-e-commerce.apollographos.net/graphql", }); function createApolloClient() { return new ApolloClient({ - ssrMode: typeof window === 'undefined', - link: from([errorLink, delayLink, typeof window === "undefined" ? schemaLink : httpLink]), + ssrMode: typeof window === "undefined", + link: from([ + errorLink, + delayLink, + typeof window === "undefined" ? schemaLink : httpLink, + ]), cache: new InMemoryCache(), }); } @@ -75,7 +79,7 @@ export function initializeApollo( _apolloClient.cache.restore(data); } // For SSG and SSR always create a new Apollo Client - if (typeof window === 'undefined') return _apolloClient; + if (typeof window === "undefined") return _apolloClient; // Create the Apollo Client once in the client if (!apolloClient) apolloClient = _apolloClient; return _apolloClient; diff --git a/integration-tests/next/src/libs/schemaLink.ts b/integration-tests/next/src/libs/schemaLink.ts index 150a9abb867..9e7b7b96e53 100644 --- a/integration-tests/next/src/libs/schemaLink.ts +++ b/integration-tests/next/src/libs/schemaLink.ts @@ -1,6 +1,6 @@ import { makeExecutableSchema } from "@graphql-tools/schema"; import { gql } from "graphql-tag"; -import { SchemaLink } from '@apollo/client/link/schema' +import { SchemaLink } from "@apollo/client/link/schema"; const typeDefs = gql` type Product { @@ -48,4 +48,4 @@ export const schema = makeExecutableSchema({ resolvers, }); -export const schemaLink = new SchemaLink({ schema }); \ No newline at end of file +export const schemaLink = new SchemaLink({ schema }); diff --git a/integration-tests/next/src/pages/_app.tsx b/integration-tests/next/src/pages/_app.tsx index 99739d91bc3..48c3fa2e958 100644 --- a/integration-tests/next/src/pages/_app.tsx +++ b/integration-tests/next/src/pages/_app.tsx @@ -1,6 +1,6 @@ -import { ApolloProvider } from '@apollo/client'; -import { useApollo } from '../libs/apolloClient'; -import type { AppProps } from 'next/app'; +import { ApolloProvider } from "@apollo/client"; +import { useApollo } from "../libs/apolloClient"; +import type { AppProps } from "next/app"; export default function App({ Component, pageProps }: AppProps) { const apolloClient = useApollo(pageProps); diff --git a/integration-tests/next/src/pages/pages-no-ssr.tsx b/integration-tests/next/src/pages/pages-no-ssr.tsx index c12827fb6fc..0e6e43e297f 100644 --- a/integration-tests/next/src/pages/pages-no-ssr.tsx +++ b/integration-tests/next/src/pages/pages-no-ssr.tsx @@ -1,7 +1,7 @@ -'use client'; +"use client"; -import type { TypedDocumentNode } from '@apollo/client'; -import { gql, useQuery } from '@apollo/client'; +import type { TypedDocumentNode } from "@apollo/client"; +import { gql, useQuery } from "@apollo/client"; const QUERY: TypedDocumentNode<{ products: { diff --git a/integration-tests/next/src/pages/pages.tsx b/integration-tests/next/src/pages/pages.tsx index 156ed31fd61..9bbc5e035dc 100644 --- a/integration-tests/next/src/pages/pages.tsx +++ b/integration-tests/next/src/pages/pages.tsx @@ -1,9 +1,9 @@ -'use client'; +"use client"; -import type { TypedDocumentNode } from '@apollo/client'; -import { gql, useQuery } from '@apollo/client'; -import type { GetStaticProps } from 'next'; -import { addApolloState, initializeApollo } from '@/libs/apolloClient'; +import type { TypedDocumentNode } from "@apollo/client"; +import { gql, useQuery } from "@apollo/client"; +import type { GetStaticProps } from "next"; +import { addApolloState, initializeApollo } from "@/libs/apolloClient"; const QUERY: TypedDocumentNode<{ products: { @@ -23,7 +23,7 @@ export default function Page() { const { data } = useQuery(QUERY); if (!data) { - throw new Error('should not happen, we have getServerSideProps!'); + throw new Error("should not happen, we have getServerSideProps!"); } return ( diff --git a/integration-tests/next/tests/playwright/apollo-client.test.ts b/integration-tests/next/tests/playwright/apollo-client.test.ts index ebc61965a06..cf4878116c0 100644 --- a/integration-tests/next/tests/playwright/apollo-client.test.ts +++ b/integration-tests/next/tests/playwright/apollo-client.test.ts @@ -1,28 +1,28 @@ -import { expect } from '@playwright/test'; -import { test } from 'shared/fixture'; +import { expect } from "@playwright/test"; +import { test } from "shared/fixture"; -test('RSC', async ({ page, blockRequest }) => { - await page.goto('http://localhost:3000'); +test("RSC", async ({ page, blockRequest }) => { + await page.goto("http://localhost:3000"); - await expect(page.getByText('Soft Warm Apollo Beanie')).toBeVisible(); + await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); }); -test('CC', async ({ page, blockRequest }) => { - await page.goto('http://localhost:3000/cc'); +test("CC", async ({ page, blockRequest }) => { + await page.goto("http://localhost:3000/cc"); - await expect(page.getByText('Soft Warm Apollo Beanie')).toBeVisible(); + await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); }); -test('Pages', async ({ page, blockRequest }) => { - await page.goto('http://localhost:3000/pages'); +test("Pages", async ({ page, blockRequest }) => { + await page.goto("http://localhost:3000/pages"); - await expect(page.getByText('Soft Warm Apollo Beanie')).toBeVisible(); + await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); }); -test('Pages without SSR', async ({ page, withHar }) => { - await page.goto('http://localhost:3000/pages-no-ssr'); +test("Pages without SSR", async ({ page, withHar }) => { + await page.goto("http://localhost:3000/pages-no-ssr"); - await expect(page.getByText('loading')).toBeVisible(); - await expect(page.getByText('loading')).not.toBeVisible(); - await expect(page.getByText('Soft Warm Apollo Beanie')).toBeVisible(); + await expect(page.getByText("loading")).toBeVisible(); + await expect(page.getByText("loading")).not.toBeVisible(); + await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); }); diff --git a/integration-tests/node-esm/test-cjs.cjs b/integration-tests/node-esm/test-cjs.cjs index b969590ff49..9cd00f4913c 100644 --- a/integration-tests/node-esm/test-cjs.cjs +++ b/integration-tests/node-esm/test-cjs.cjs @@ -1,11 +1,11 @@ -const assert = require('node:assert'); -const path = require('path'); +const assert = require("node:assert"); +const path = require("path"); -const { ApolloClient } = require('@apollo/client'); -const { useQuery } = require('@apollo/client/react'); -const { HttpLink } = require('@apollo/client/link/http'); +const { ApolloClient } = require("@apollo/client"); +const { useQuery } = require("@apollo/client/react"); +const { HttpLink } = require("@apollo/client/link/http"); -console.log('Testing Node with CJS imports...'); +console.log("Testing Node with CJS imports..."); function checkFunctionName(fn, name, category) { console.log(`Checking ${category} '${name}' === '${fn.name}'`); @@ -16,9 +16,9 @@ function checkFunctionName(fn, name, category) { } const entries = [ - [ApolloClient, 'ApolloClient', 'Barrel Import'], - [useQuery, 'useQuery', 'Apollo React'], - [HttpLink, 'HttpLink', 'Link'], + [ApolloClient, "ApolloClient", "Barrel Import"], + [useQuery, "useQuery", "Apollo React"], + [HttpLink, "HttpLink", "Link"], ]; for (let [fn, name, category] of entries) { @@ -30,9 +30,9 @@ for (let [fn, name, category] of entries) { } const moduleNames = [ - ['@apollo/client', '/main.cjs'], - ['@apollo/client/react', '/react/react.cjs'], - ['@apollo/client/link/http', '/link/http/http.cjs'], + ["@apollo/client", "/main.cjs"], + ["@apollo/client/react", "/react/react.cjs"], + ["@apollo/client/link/http", "/link/http/http.cjs"], ]; for (let [moduleName, expectedFilename] of moduleNames) { @@ -42,4 +42,4 @@ for (let [moduleName, expectedFilename] of moduleNames) { assert(posixPath.endsWith(expectedFilename)); } -console.log('CJS test succeeded'); +console.log("CJS test succeeded"); diff --git a/integration-tests/node-esm/test-esm.mjs b/integration-tests/node-esm/test-esm.mjs index 7b7629267b7..22d59040d08 100644 --- a/integration-tests/node-esm/test-esm.mjs +++ b/integration-tests/node-esm/test-esm.mjs @@ -1,15 +1,15 @@ // TODO This entire file doesn't work yet without appending `/index.js` to all imports manually! -import assert from 'node:assert'; -import path from 'path'; -import { importMetaResolve } from 'resolve-esm'; +import assert from "node:assert"; +import path from "path"; +import { importMetaResolve } from "resolve-esm"; -import { ApolloClient } from '@apollo/client/index.js'; -import { useQuery } from '@apollo/client/react/index.js'; -import { HttpLink } from '@apollo/client/link/http/index.js'; +import { ApolloClient } from "@apollo/client/index.js"; +import { useQuery } from "@apollo/client/react/index.js"; +import { HttpLink } from "@apollo/client/link/http/index.js"; console.log( - 'Testing Node with ESM imports... (user-side workaround with `/index.js)' + "Testing Node with ESM imports... (user-side workaround with `/index.js)" ); function checkFunctionName(fn, name, category) { @@ -21,9 +21,9 @@ function checkFunctionName(fn, name, category) { } const entries = [ - [ApolloClient, 'ApolloClient', 'Barrel Import'], - [useQuery, 'useQuery', 'Apollo React'], - [HttpLink, 'HttpLink', 'Link'], + [ApolloClient, "ApolloClient", "Barrel Import"], + [useQuery, "useQuery", "Apollo React"], + [HttpLink, "HttpLink", "Link"], ]; for (let [fn, name, category] of entries) { @@ -35,9 +35,9 @@ for (let [fn, name, category] of entries) { } const moduleNames = [ - ['@apollo/client/index.js', '/index.js'], - ['@apollo/client/react/index.js', '/react/index.js'], - ['@apollo/client/link/http/index.js', '/link/http/index.js'], + ["@apollo/client/index.js", "/index.js"], + ["@apollo/client/react/index.js", "/react/index.js"], + ["@apollo/client/link/http/index.js", "/link/http/index.js"], ]; (async () => { diff --git a/integration-tests/node-standard/test-cjs.js b/integration-tests/node-standard/test-cjs.js index b969590ff49..9cd00f4913c 100644 --- a/integration-tests/node-standard/test-cjs.js +++ b/integration-tests/node-standard/test-cjs.js @@ -1,11 +1,11 @@ -const assert = require('node:assert'); -const path = require('path'); +const assert = require("node:assert"); +const path = require("path"); -const { ApolloClient } = require('@apollo/client'); -const { useQuery } = require('@apollo/client/react'); -const { HttpLink } = require('@apollo/client/link/http'); +const { ApolloClient } = require("@apollo/client"); +const { useQuery } = require("@apollo/client/react"); +const { HttpLink } = require("@apollo/client/link/http"); -console.log('Testing Node with CJS imports...'); +console.log("Testing Node with CJS imports..."); function checkFunctionName(fn, name, category) { console.log(`Checking ${category} '${name}' === '${fn.name}'`); @@ -16,9 +16,9 @@ function checkFunctionName(fn, name, category) { } const entries = [ - [ApolloClient, 'ApolloClient', 'Barrel Import'], - [useQuery, 'useQuery', 'Apollo React'], - [HttpLink, 'HttpLink', 'Link'], + [ApolloClient, "ApolloClient", "Barrel Import"], + [useQuery, "useQuery", "Apollo React"], + [HttpLink, "HttpLink", "Link"], ]; for (let [fn, name, category] of entries) { @@ -30,9 +30,9 @@ for (let [fn, name, category] of entries) { } const moduleNames = [ - ['@apollo/client', '/main.cjs'], - ['@apollo/client/react', '/react/react.cjs'], - ['@apollo/client/link/http', '/link/http/http.cjs'], + ["@apollo/client", "/main.cjs"], + ["@apollo/client/react", "/react/react.cjs"], + ["@apollo/client/link/http", "/link/http/http.cjs"], ]; for (let [moduleName, expectedFilename] of moduleNames) { @@ -42,4 +42,4 @@ for (let [moduleName, expectedFilename] of moduleNames) { assert(posixPath.endsWith(expectedFilename)); } -console.log('CJS test succeeded'); +console.log("CJS test succeeded"); diff --git a/integration-tests/node-standard/test-esm.mjs b/integration-tests/node-standard/test-esm.mjs index 785459db69b..3923818778e 100644 --- a/integration-tests/node-standard/test-esm.mjs +++ b/integration-tests/node-standard/test-esm.mjs @@ -1,15 +1,15 @@ // TODO This entire file doesn't work yet without appending `/index.js` to all imports manually! -import assert from 'node:assert'; -import path from 'path'; -import { importMetaResolve } from 'resolve-esm'; +import assert from "node:assert"; +import path from "path"; +import { importMetaResolve } from "resolve-esm"; -import { ApolloClient } from '@apollo/client/index.js'; -import { useQuery } from '@apollo/client/react/index.js'; -import { HttpLink } from '@apollo/client/link/http/index.js'; +import { ApolloClient } from "@apollo/client/index.js"; +import { useQuery } from "@apollo/client/react/index.js"; +import { HttpLink } from "@apollo/client/link/http/index.js"; console.log( - 'Testing Node with ESM imports... (user-side workaround with `/index.js)' + "Testing Node with ESM imports... (user-side workaround with `/index.js)" ); function checkFunctionName(fn, name, category) { @@ -21,9 +21,9 @@ function checkFunctionName(fn, name, category) { } const entries = [ - [ApolloClient, 'ApolloClient', 'Barrel Import'], - [useQuery, 'useQuery', 'Apollo React'], - [HttpLink, 'HttpLink', 'Link'], + [ApolloClient, "ApolloClient", "Barrel Import"], + [useQuery, "useQuery", "Apollo React"], + [HttpLink, "HttpLink", "Link"], ]; for (let [fn, name, category] of entries) { @@ -35,9 +35,9 @@ for (let [fn, name, category] of entries) { } const moduleNames = [ - ['@apollo/client/index.js', '/index.js'], - ['@apollo/client/react/index.js', '/react/index.js'], - ['@apollo/client/link/http/index.js', '/link/http/index.js'], + ["@apollo/client/index.js", "/index.js"], + ["@apollo/client/react/index.js", "/react/index.js"], + ["@apollo/client/link/http/index.js", "/link/http/index.js"], ]; (async () => { diff --git a/integration-tests/shared/fixture.ts b/integration-tests/shared/fixture.ts index ffc9a4eebf9..2a074187b7c 100644 --- a/integration-tests/shared/fixture.ts +++ b/integration-tests/shared/fixture.ts @@ -1,26 +1,26 @@ -import { test as base, expect } from '@playwright/test'; +import { test as base, expect } from "@playwright/test"; export const test = base.extend<{ - withHar: import('@playwright/test').Page; - blockRequest: import('@playwright/test').Page; + withHar: import("@playwright/test").Page; + blockRequest: import("@playwright/test").Page; }>({ page: async ({ page }, use) => { - page.on('pageerror', (error) => { - expect(error.stack || error).toBe('no error'); + page.on("pageerror", (error) => { + expect(error.stack || error).toBe("no error"); }); await use(page); }, withHar: async ({ page }, use) => { - await page.routeFromHAR('../api.har', { - url: '**/graphql', - notFound: 'abort', + await page.routeFromHAR("../api.har", { + url: "**/graphql", + notFound: "abort", }); await use(page); }, blockRequest: async ({ page }, use) => { - await page.routeFromHAR('../empty.har', { - url: '**/graphql', - notFound: 'abort', + await page.routeFromHAR("../empty.har", { + url: "**/graphql", + notFound: "abort", }); await use(page); }, diff --git a/integration-tests/shared/playwright.config.ts b/integration-tests/shared/playwright.config.ts index 4d83fb2ad5a..05f49089f5e 100644 --- a/integration-tests/shared/playwright.config.ts +++ b/integration-tests/shared/playwright.config.ts @@ -1,7 +1,7 @@ export const baseConfig = { webServer: { - command: 'yarn serve-app', - url: 'http://localhost:3000', + command: "yarn serve-app", + url: "http://localhost:3000", timeout: 120 * 1000, reuseExistingServer: !process.env.CI, }, @@ -11,5 +11,5 @@ export const baseConfig = { viewport: { width: 1280, height: 720 }, ignoreHTTPSErrors: true, }, - testDir: 'tests/playwright/', + testDir: "tests/playwright/", } as const; diff --git a/integration-tests/vite-swc/index.html b/integration-tests/vite-swc/index.html index e4b5d91060e..d5279fc3842 100644 --- a/integration-tests/vite-swc/index.html +++ b/integration-tests/vite-swc/index.html @@ -1,15 +1,13 @@ - + + + + + Vite + React + TS + - - - - Vite + React + TS - - - -
- - - + +
+ + diff --git a/integration-tests/vite-swc/playwright.config.ts b/integration-tests/vite-swc/playwright.config.ts index 529168edd81..606e059e375 100644 --- a/integration-tests/vite-swc/playwright.config.ts +++ b/integration-tests/vite-swc/playwright.config.ts @@ -1,4 +1,4 @@ -import { baseConfig } from 'shared/playwright.config.ts'; -import { defineConfig } from '@playwright/test'; +import { baseConfig } from "shared/playwright.config.ts"; +import { defineConfig } from "@playwright/test"; export default defineConfig(baseConfig); diff --git a/integration-tests/vite-swc/src/App.tsx b/integration-tests/vite-swc/src/App.tsx index e411245729a..cf87849afbd 100644 --- a/integration-tests/vite-swc/src/App.tsx +++ b/integration-tests/vite-swc/src/App.tsx @@ -1,4 +1,4 @@ -import type { TypedDocumentNode } from '@apollo/client'; +import type { TypedDocumentNode } from "@apollo/client"; import { useQuery, @@ -9,7 +9,7 @@ import { ApolloLink, Observable, HttpLink, -} from '@apollo/client'; +} from "@apollo/client"; const delayLink = new ApolloLink((operation, forward) => { return new Observable((observer) => { @@ -22,7 +22,7 @@ const delayLink = new ApolloLink((operation, forward) => { }); const httpLink = new HttpLink({ - uri: 'https://main--hack-the-e-commerce.apollographos.net/graphql', + uri: "https://main--hack-the-e-commerce.apollographos.net/graphql", }); const client = new ApolloClient({ @@ -56,11 +56,7 @@ function Main() { const { data } = useQuery(QUERY); return data ? ( -
    - {data?.products.map(({ id, title }) => ( -
  • {title}
  • - ))} -
+
    {data?.products.map(({ id, title }) =>
  • {title}
  • )}
) : ( <>loading ); diff --git a/integration-tests/vite-swc/src/main.tsx b/integration-tests/vite-swc/src/main.tsx index a6ac0e20918..64eacd7a146 100644 --- a/integration-tests/vite-swc/src/main.tsx +++ b/integration-tests/vite-swc/src/main.tsx @@ -1,8 +1,8 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import App from './App.tsx'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App.tsx"; -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( diff --git a/integration-tests/vite-swc/tests/playwright/apollo-client.test.ts b/integration-tests/vite-swc/tests/playwright/apollo-client.test.ts index 7ffb0760a55..4b210a249cb 100644 --- a/integration-tests/vite-swc/tests/playwright/apollo-client.test.ts +++ b/integration-tests/vite-swc/tests/playwright/apollo-client.test.ts @@ -1,10 +1,10 @@ -import { expect } from '@playwright/test'; -import { test } from 'shared/fixture.ts'; +import { expect } from "@playwright/test"; +import { test } from "shared/fixture.ts"; -test('Basic Test', async ({ page, withHar }) => { - await page.goto('http://localhost:3000'); +test("Basic Test", async ({ page, withHar }) => { + await page.goto("http://localhost:3000"); - await expect(page.getByText('loading')).toBeVisible(); - await expect(page.getByText('loading')).not.toBeVisible(); - await expect(page.getByText('Soft Warm Apollo Beanie')).toBeVisible(); + await expect(page.getByText("loading")).toBeVisible(); + await expect(page.getByText("loading")).not.toBeVisible(); + await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); }); diff --git a/integration-tests/vite-swc/vite.config.ts b/integration-tests/vite-swc/vite.config.ts index 861b04b3560..d366e8c8d7c 100644 --- a/integration-tests/vite-swc/vite.config.ts +++ b/integration-tests/vite-swc/vite.config.ts @@ -1,7 +1,7 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react-swc' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react-swc"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], -}) +}); diff --git a/integration-tests/vite/index.html b/integration-tests/vite/index.html index e4b5d91060e..d5279fc3842 100644 --- a/integration-tests/vite/index.html +++ b/integration-tests/vite/index.html @@ -1,15 +1,13 @@ - + + + + + Vite + React + TS + - - - - Vite + React + TS - - - -
- - - + +
+ + diff --git a/integration-tests/vite/playwright.config.ts b/integration-tests/vite/playwright.config.ts index 529168edd81..606e059e375 100644 --- a/integration-tests/vite/playwright.config.ts +++ b/integration-tests/vite/playwright.config.ts @@ -1,4 +1,4 @@ -import { baseConfig } from 'shared/playwright.config.ts'; -import { defineConfig } from '@playwright/test'; +import { baseConfig } from "shared/playwright.config.ts"; +import { defineConfig } from "@playwright/test"; export default defineConfig(baseConfig); diff --git a/integration-tests/vite/src/App.tsx b/integration-tests/vite/src/App.tsx index e411245729a..cf87849afbd 100644 --- a/integration-tests/vite/src/App.tsx +++ b/integration-tests/vite/src/App.tsx @@ -1,4 +1,4 @@ -import type { TypedDocumentNode } from '@apollo/client'; +import type { TypedDocumentNode } from "@apollo/client"; import { useQuery, @@ -9,7 +9,7 @@ import { ApolloLink, Observable, HttpLink, -} from '@apollo/client'; +} from "@apollo/client"; const delayLink = new ApolloLink((operation, forward) => { return new Observable((observer) => { @@ -22,7 +22,7 @@ const delayLink = new ApolloLink((operation, forward) => { }); const httpLink = new HttpLink({ - uri: 'https://main--hack-the-e-commerce.apollographos.net/graphql', + uri: "https://main--hack-the-e-commerce.apollographos.net/graphql", }); const client = new ApolloClient({ @@ -56,11 +56,7 @@ function Main() { const { data } = useQuery(QUERY); return data ? ( -
    - {data?.products.map(({ id, title }) => ( -
  • {title}
  • - ))} -
+
    {data?.products.map(({ id, title }) =>
  • {title}
  • )}
) : ( <>loading ); diff --git a/integration-tests/vite/src/main.tsx b/integration-tests/vite/src/main.tsx index 80d56b3d540..64eacd7a146 100644 --- a/integration-tests/vite/src/main.tsx +++ b/integration-tests/vite/src/main.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App.tsx' +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App.tsx"; -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - , -) +
+); diff --git a/integration-tests/vite/tests/playwright/apollo-client.test.ts b/integration-tests/vite/tests/playwright/apollo-client.test.ts index 7ffb0760a55..4b210a249cb 100644 --- a/integration-tests/vite/tests/playwright/apollo-client.test.ts +++ b/integration-tests/vite/tests/playwright/apollo-client.test.ts @@ -1,10 +1,10 @@ -import { expect } from '@playwright/test'; -import { test } from 'shared/fixture.ts'; +import { expect } from "@playwright/test"; +import { test } from "shared/fixture.ts"; -test('Basic Test', async ({ page, withHar }) => { - await page.goto('http://localhost:3000'); +test("Basic Test", async ({ page, withHar }) => { + await page.goto("http://localhost:3000"); - await expect(page.getByText('loading')).toBeVisible(); - await expect(page.getByText('loading')).not.toBeVisible(); - await expect(page.getByText('Soft Warm Apollo Beanie')).toBeVisible(); + await expect(page.getByText("loading")).toBeVisible(); + await expect(page.getByText("loading")).not.toBeVisible(); + await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); }); diff --git a/integration-tests/vite/vite.config.ts b/integration-tests/vite/vite.config.ts index 5a33944a9b4..9cc50ead1c0 100644 --- a/integration-tests/vite/vite.config.ts +++ b/integration-tests/vite/vite.config.ts @@ -1,7 +1,7 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], -}) +}); diff --git a/package-lock.json b/package-lock.json index b4d2b07fb12..61be262566d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,7 @@ "jest-junit": "16.0.0", "lodash": "4.17.21", "patch-package": "7.0.0", - "prettier": "2.7.1", + "prettier": "^3.0.0", "react": "18.2.0", "react-17": "npm:react@^17", "react-dom": "18.2.0", @@ -784,6 +784,21 @@ "semver": "^7.5.3" } }, + "node_modules/@changesets/apply-release-plan/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/@changesets/apply-release-plan/node_modules/semver": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", @@ -1212,6 +1227,21 @@ "prettier": "^2.7.1" } }, + "node_modules/@changesets/write/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -9217,15 +9247,15 @@ } }, "node_modules/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", + "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" diff --git a/package.json b/package.json index 472eb0a59b2..2d5af70ea26 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ "jest-junit": "16.0.0", "lodash": "4.17.21", "patch-package": "7.0.0", - "prettier": "2.7.1", + "prettier": "^3.0.0", "react": "18.2.0", "react-17": "npm:react@^17", "react-dom": "18.2.0", @@ -169,13 +169,5 @@ }, "publishConfig": { "access": "public" - }, - "prettier": { - "bracketSpacing": true, - "printWidth": 80, - "semi": true, - "singleQuote": true, - "tabWidth": 2, - "trailingComma": "es5" } } diff --git a/src/core/__tests__/equalByQuery.ts b/src/core/__tests__/equalByQuery.ts index b3028591b78..29d75d201a5 100644 --- a/src/core/__tests__/equalByQuery.ts +++ b/src/core/__tests__/equalByQuery.ts @@ -14,65 +14,49 @@ describe("equalByQuery", () => { } `; - expect(equalByQuery( - query, - { data: { hello: "hi" } }, - { data: { hello: "hi" } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { hello: "hi", unrelated: 1 } }, - { data: { hello: "hi", unrelated: 100 } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { hello: "hi" } }, - { data: { hello: "hey" } }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: {} }, - { data: { hello: "hi" } }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: { hello: "hi" } }, - { data: {} }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: { hello: "hi" } }, - { data: null }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: null }, - { data: { hello: "hi" } }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: null }, - { data: null }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: {} }, - { data: {} }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { unrelated: "whatever" } }, - { data: { unrelated: "no matter" } }, - )).toBe(true); + expect( + equalByQuery(query, { data: { hello: "hi" } }, { data: { hello: "hi" } }) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: { hello: "hi", unrelated: 1 } }, + { data: { hello: "hi", unrelated: 100 } } + ) + ).toBe(true); + + expect( + equalByQuery(query, { data: { hello: "hi" } }, { data: { hello: "hey" } }) + ).toBe(false); + + expect(equalByQuery(query, { data: {} }, { data: { hello: "hi" } })).toBe( + false + ); + + expect(equalByQuery(query, { data: { hello: "hi" } }, { data: {} })).toBe( + false + ); + + expect(equalByQuery(query, { data: { hello: "hi" } }, { data: null })).toBe( + false + ); + + expect(equalByQuery(query, { data: null }, { data: { hello: "hi" } })).toBe( + false + ); + + expect(equalByQuery(query, { data: null }, { data: null })).toBe(true); + + expect(equalByQuery(query, { data: {} }, { data: {} })).toBe(true); + + expect( + equalByQuery( + query, + { data: { unrelated: "whatever" } }, + { data: { unrelated: "no matter" } } + ) + ).toBe(true); }); it("is not confused by properties in different orders", () => { @@ -84,17 +68,21 @@ describe("equalByQuery", () => { } `; - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { b: 2, c: 3, a: 1 } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { d: "bogus", a: 1, b: 2, c: 3 } }, - { data: { b: 2, c: 3, a: 1, d: "also bogus" } }, - )).toBe(true); + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { b: 2, c: 3, a: 1 } } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: { d: "bogus", a: 1, b: 2, c: 3 } }, + { data: { b: 2, c: 3, a: 1, d: "also bogus" } } + ) + ).toBe(true); }); it("respects the @nonreactive directive on fields", () => { @@ -106,17 +94,21 @@ describe("equalByQuery", () => { } `; - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 2, c: "different" } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: "different", b: 2, c: 4 } }, - )).toBe(false); + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 2, c: "different" } } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: "different", b: 2, c: 4 } } + ) + ).toBe(false); }); describe("@skip and @include directives", () => { @@ -128,120 +120,148 @@ describe("equalByQuery", () => { // execution of nonreactive fields/subtrees on the server. it("respects @skip directive, depending on variables", () => { const skipQuery = gql` - query SkipC ($condition: Boolean!) { + query SkipC($condition: Boolean!) { a b c @skip(if: $condition) } `; - expect(equalByQuery( - skipQuery, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 2, c: 3 } }, - { condition: false }, - )).toBe(true); - - expect(equalByQuery( - skipQuery, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 2 } }, - { condition: false }, - )).toBe(false); - - expect(equalByQuery( - skipQuery, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 2 } }, - { condition: true }, - )).toBe(true); - - expect(equalByQuery( - skipQuery, - { data: { a: 1, b: 2 } }, - { data: { a: 1, b: 2, c: 3 } }, - { condition: false }, - )).toBe(false); - - expect(equalByQuery( - skipQuery, - { data: { a: 1, b: 2 } }, - { data: { a: 1, b: 2, c: 3 } }, - { condition: true }, - )).toBe(true); - - expect(equalByQuery( - skipQuery, - { data: { a: 1, b: 2 } }, - { data: { a: 1, b: 2 } }, - { condition: false }, - )).toBe(true); - - expect(equalByQuery( - skipQuery, - { data: { a: 1, b: 2 } }, - { data: { a: 1, b: 2 } }, - { condition: true }, - )).toBe(true); + expect( + equalByQuery( + skipQuery, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 2, c: 3 } }, + { condition: false } + ) + ).toBe(true); + + expect( + equalByQuery( + skipQuery, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 2 } }, + { condition: false } + ) + ).toBe(false); + + expect( + equalByQuery( + skipQuery, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 2 } }, + { condition: true } + ) + ).toBe(true); + + expect( + equalByQuery( + skipQuery, + { data: { a: 1, b: 2 } }, + { data: { a: 1, b: 2, c: 3 } }, + { condition: false } + ) + ).toBe(false); + + expect( + equalByQuery( + skipQuery, + { data: { a: 1, b: 2 } }, + { data: { a: 1, b: 2, c: 3 } }, + { condition: true } + ) + ).toBe(true); + + expect( + equalByQuery( + skipQuery, + { data: { a: 1, b: 2 } }, + { data: { a: 1, b: 2 } }, + { condition: false } + ) + ).toBe(true); + + expect( + equalByQuery( + skipQuery, + { data: { a: 1, b: 2 } }, + { data: { a: 1, b: 2 } }, + { condition: true } + ) + ).toBe(true); }); it("respects @include directive, depending on variables", () => { const includeQuery = gql` - query IncludeC ($condition: Boolean!) { + query IncludeC($condition: Boolean!) { a b c @include(if: $condition) } `; - expect(equalByQuery( - includeQuery, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 2, c: 3 } }, - { condition: true }, - )).toBe(true); - - expect(equalByQuery( - includeQuery, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 2 } }, - { condition: true }, - )).toBe(false); - - expect(equalByQuery( - includeQuery, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 2 } }, - { condition: false }, - )).toBe(true); - - expect(equalByQuery( - includeQuery, - { data: { a: 1, b: 2 } }, - { data: { a: 1, b: 2, c: 3 } }, - { condition: true }, - )).toBe(false); - - expect(equalByQuery( - includeQuery, - { data: { a: 1, b: 2 } }, - { data: { a: 1, b: 2, c: 3 } }, - { condition: false }, - )).toBe(true); - - expect(equalByQuery( - includeQuery, - { data: { a: 1, b: 2 } }, - { data: { a: 1, b: 2 } }, - { condition: true }, - )).toBe(true); - - expect(equalByQuery( - includeQuery, - { data: { a: 1, b: 2 } }, - { data: { a: 1, b: 2 } }, - { condition: false }, - )).toBe(true); + expect( + equalByQuery( + includeQuery, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 2, c: 3 } }, + { condition: true } + ) + ).toBe(true); + + expect( + equalByQuery( + includeQuery, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 2 } }, + { condition: true } + ) + ).toBe(false); + + expect( + equalByQuery( + includeQuery, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 2 } }, + { condition: false } + ) + ).toBe(true); + + expect( + equalByQuery( + includeQuery, + { data: { a: 1, b: 2 } }, + { data: { a: 1, b: 2, c: 3 } }, + { condition: true } + ) + ).toBe(false); + + expect( + equalByQuery( + includeQuery, + { data: { a: 1, b: 2 } }, + { data: { a: 1, b: 2, c: 3 } }, + { condition: false } + ) + ).toBe(true); + + expect( + equalByQuery( + includeQuery, + { data: { a: 1, b: 2 } }, + { data: { a: 1, b: 2 } }, + { condition: true } + ) + ).toBe(true); + + expect( + equalByQuery( + includeQuery, + { data: { a: 1, b: 2 } }, + { data: { a: 1, b: 2 } }, + { condition: false } + ) + ).toBe(true); }); }); @@ -258,53 +278,69 @@ describe("equalByQuery", () => { const oopsError = new GraphQLError("oops"); const differentError = new GraphQLError("different"); - expect(equalByQuery( - query, - { data: data123 }, - { data: data123, errors: [oopsError] }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: data123, errors: [oopsError] }, - { data: data123 }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: data123, errors: [oopsError] }, - { data: data123, errors: [oopsError] }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: data123, errors: [oopsError] }, - { data: data123, errors: [differentError] }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: data123, errors: [oopsError] }, - { data: data123, errors: [oopsError] }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: data123, errors: [oopsError] }, - { data: { ...data123, b: 100 }, errors: [oopsError] }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: data123, errors: [] }, - { data: data123, errors: [] }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: data123, errors: [] }, - { data: { ...data123, b: 100 }, errors: [] }, - )).toBe(true); + expect( + equalByQuery( + query, + { data: data123 }, + { data: data123, errors: [oopsError] } + ) + ).toBe(false); + + expect( + equalByQuery( + query, + { data: data123, errors: [oopsError] }, + { data: data123 } + ) + ).toBe(false); + + expect( + equalByQuery( + query, + { data: data123, errors: [oopsError] }, + { data: data123, errors: [oopsError] } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: data123, errors: [oopsError] }, + { data: data123, errors: [differentError] } + ) + ).toBe(false); + + expect( + equalByQuery( + query, + { data: data123, errors: [oopsError] }, + { data: data123, errors: [oopsError] } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: data123, errors: [oopsError] }, + { data: { ...data123, b: 100 }, errors: [oopsError] } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: data123, errors: [] }, + { data: data123, errors: [] } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: data123, errors: [] }, + { data: { ...data123, b: 100 }, errors: [] } + ) + ).toBe(true); }); it("respects the @nonreactive directive on inline fragments", () => { @@ -318,17 +354,21 @@ describe("equalByQuery", () => { } `; - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 20, c: 30 } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 10, b: 20, c: 30 } }, - )).toBe(false); + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 20, c: 30 } } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 10, b: 20, c: 30 } } + ) + ).toBe(false); }); it("respects the @nonreactive directive on named fragment ...spreads", () => { @@ -344,29 +384,37 @@ describe("equalByQuery", () => { } `; - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 2, c: 30 } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 20, c: 3 } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 20, c: 30 } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 10, b: 20, c: 30 } }, - )).toBe(false); + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 2, c: 30 } } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 20, c: 3 } } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 20, c: 30 } } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 10, b: 20, c: 30 } } + ) + ).toBe(false); }); it("respects the @nonreactive directive on named fragment definitions", () => { @@ -382,29 +430,37 @@ describe("equalByQuery", () => { } `; - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 2, c: 30 } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 20, c: 3 } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 20, c: 30 } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 10, b: 20, c: 30 } }, - )).toBe(false); + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 2, c: 30 } } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 20, c: 3 } } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 20, c: 30 } } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 10, b: 20, c: 30 } } + ) + ).toBe(false); }); it("traverses fragments without @nonreactive", () => { @@ -420,41 +476,53 @@ describe("equalByQuery", () => { } `; - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 2, c: 3 } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { c: 3, a: 1, b: 2 } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 2, c: 30 } }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 20, c: 3 } }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 1, b: 20, c: 30 } }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: { a: 1, b: 2, c: 3 } }, - { data: { a: 10, b: 20, c: 30 } }, - )).toBe(false); + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 2, c: 3 } } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { c: 3, a: 1, b: 2 } } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 2, c: 30 } } + ) + ).toBe(false); + + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 20, c: 3 } } + ) + ).toBe(false); + + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 1, b: 20, c: 30 } } + ) + ).toBe(false); + + expect( + equalByQuery( + query, + { data: { a: 1, b: 2, c: 3 } }, + { data: { a: 10, b: 20, c: 30 } } + ) + ).toBe(false); }); type Thing = { @@ -539,99 +607,116 @@ describe("equalByQuery", () => { } } `, - ])("iterates over array-valued result fields ignoring @nonreactive (%#)", query => { - let nextVolatileIntegerPart = 0; - const makeThing = (id: string, stable = 1): Thing => ({ - __typename: "Thing", - id, - stable, - // Thing.volatile is always a different randomized number, which normally - // would threatens any deep comparison of Thing objects. These test cases - // demonstrate (among other things) that we can make the result comparison - // insensitive to this volatility by marking the volatile field with the - // @nonreactive directive. - volatile: (nextVolatileIntegerPart++) + Math.random(), - }); - - const makeThings = (lettersToSplit: string, stable: number = 1): Thing[] => - lettersToSplit.split("").map(id => makeThing(id, stable)); - - expect(equalByQuery( - query, - { data: { things: makeThings("abc") } }, - { data: { things: [makeThing("a"), makeThing("b"), makeThing("c")] } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { things: makeThings("abcdefg", 2) } }, - { data: { things: makeThings("abcdefg") } }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: { things: makeThings("abcdefg", 2) } }, - { data: { things: makeThings("abcdefg", 3) } }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: { things: makeThings("abcdefg", 3) } }, - { data: { things: makeThings("abcdefg", 3) } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { things: makeThings("ab", 2345) } }, - { data: { things: [makeThing("a"), makeThing("b", 2345)] } }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: { things: makeThings("ab", 3456) } }, - { data: { things: [makeThing("a", 3456), makeThing("b")] } }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: { things: makeThings("ab", 3456) } }, - { data: { things: [makeThing("a", 3456), makeThing("b", 3456)] } }, - )).toBe(true); - - expect(equalByQuery( - query, - { data: { things: makeThings("abc") } }, - { data: { things: "not an array" } }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: { things: {} } }, - { data: { things: [] } }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: { things: [] } }, - { data: { things: {} } }, - )).toBe(false); - - expect(equalByQuery( - query, - { data: { things: [] } }, - { data: { things: [] } }, - )).toBe(true); - - expect(equalByQuery( - query, - // There's nothing inherently array-like about the Query.things field as - // it's represented in query syntax, since `query { things { id } }` could - // (depending on the server/schema) return a single object for the things - // field, rather than an array. Although this might seem like a strange - // edge case to test, it demonstrates the equalByQuery function can handle - // any combination of array/non-array values. - { data: { things: {} } }, - { data: { things: {} } }, - )).toBe(true); - }); + ])( + "iterates over array-valued result fields ignoring @nonreactive (%#)", + (query) => { + let nextVolatileIntegerPart = 0; + const makeThing = (id: string, stable = 1): Thing => ({ + __typename: "Thing", + id, + stable, + // Thing.volatile is always a different randomized number, which normally + // would threatens any deep comparison of Thing objects. These test cases + // demonstrate (among other things) that we can make the result comparison + // insensitive to this volatility by marking the volatile field with the + // @nonreactive directive. + volatile: nextVolatileIntegerPart++ + Math.random(), + }); + + const makeThings = ( + lettersToSplit: string, + stable: number = 1 + ): Thing[] => lettersToSplit.split("").map((id) => makeThing(id, stable)); + + expect( + equalByQuery( + query, + { data: { things: makeThings("abc") } }, + { data: { things: [makeThing("a"), makeThing("b"), makeThing("c")] } } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: { things: makeThings("abcdefg", 2) } }, + { data: { things: makeThings("abcdefg") } } + ) + ).toBe(false); + + expect( + equalByQuery( + query, + { data: { things: makeThings("abcdefg", 2) } }, + { data: { things: makeThings("abcdefg", 3) } } + ) + ).toBe(false); + + expect( + equalByQuery( + query, + { data: { things: makeThings("abcdefg", 3) } }, + { data: { things: makeThings("abcdefg", 3) } } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: { things: makeThings("ab", 2345) } }, + { data: { things: [makeThing("a"), makeThing("b", 2345)] } } + ) + ).toBe(false); + + expect( + equalByQuery( + query, + { data: { things: makeThings("ab", 3456) } }, + { data: { things: [makeThing("a", 3456), makeThing("b")] } } + ) + ).toBe(false); + + expect( + equalByQuery( + query, + { data: { things: makeThings("ab", 3456) } }, + { data: { things: [makeThing("a", 3456), makeThing("b", 3456)] } } + ) + ).toBe(true); + + expect( + equalByQuery( + query, + { data: { things: makeThings("abc") } }, + { data: { things: "not an array" } } + ) + ).toBe(false); + + expect( + equalByQuery(query, { data: { things: {} } }, { data: { things: [] } }) + ).toBe(false); + + expect( + equalByQuery(query, { data: { things: [] } }, { data: { things: {} } }) + ).toBe(false); + + expect( + equalByQuery(query, { data: { things: [] } }, { data: { things: [] } }) + ).toBe(true); + + expect( + equalByQuery( + query, + // There's nothing inherently array-like about the Query.things field as + // it's represented in query syntax, since `query { things { id } }` could + // (depending on the server/schema) return a single object for the things + // field, rather than an array. Although this might seem like a strange + // edge case to test, it demonstrates the equalByQuery function can handle + // any combination of array/non-array values. + { data: { things: {} } }, + { data: { things: {} } } + ) + ).toBe(true); + } + ); }); diff --git a/src/core/equalByQuery.ts b/src/core/equalByQuery.ts index 0ac40c95558..d971061fdcf 100644 --- a/src/core/equalByQuery.ts +++ b/src/core/equalByQuery.ts @@ -1,4 +1,4 @@ -import equal from '@wry/equality'; +import equal from "@wry/equality"; import type { DirectiveNode, @@ -9,11 +9,11 @@ import type { InlineFragmentNode, SelectionNode, SelectionSetNode, -} from 'graphql'; +} from "graphql"; -import type { ApolloQueryResult, OperationVariables } from './types.js'; +import type { ApolloQueryResult, OperationVariables } from "./types.js"; -import type { FragmentMap } from '../utilities/index.js'; +import type { FragmentMap } from "../utilities/index.js"; import { createFragmentMap, getFragmentDefinitions, @@ -22,7 +22,7 @@ import { isField, resultKeyNameFromField, shouldInclude, -} from '../utilities/index.js'; +} from "../utilities/index.js"; // Returns true if aResult and bResult are deeply equal according to the fields // selected by the given query, ignoring any fields marked as @nonreactive. @@ -152,5 +152,5 @@ function selectionHasNonreactiveDirective( } function directiveIsNonreactive(dir: DirectiveNode): boolean { - return dir.name.value === 'nonreactive'; + return dir.name.value === "nonreactive"; } diff --git a/src/dev/index.ts b/src/dev/index.ts index 47b8d089dbd..7d6b01f873f 100644 --- a/src/dev/index.ts +++ b/src/dev/index.ts @@ -1,3 +1,3 @@ -export { loadDevMessages } from './loadDevMessages.js'; -export { loadErrorMessageHandler } from './loadErrorMessageHandler.js'; -export { loadErrorMessages } from './loadErrorMessages.js'; +export { loadDevMessages } from "./loadDevMessages.js"; +export { loadErrorMessageHandler } from "./loadErrorMessageHandler.js"; +export { loadErrorMessages } from "./loadErrorMessages.js"; diff --git a/src/dev/loadDevMessages.ts b/src/dev/loadDevMessages.ts index 5ad1aa130aa..f39d742231b 100644 --- a/src/dev/loadDevMessages.ts +++ b/src/dev/loadDevMessages.ts @@ -1,5 +1,5 @@ -import { devDebug, devError, devLog, devWarn } from '../invariantErrorCodes.js'; -import { loadErrorMessageHandler } from './loadErrorMessageHandler.js'; +import { devDebug, devError, devLog, devWarn } from "../invariantErrorCodes.js"; +import { loadErrorMessageHandler } from "./loadErrorMessageHandler.js"; export function loadDevMessages() { loadErrorMessageHandler(devDebug, devError, devLog, devWarn); diff --git a/src/dev/loadErrorMessageHandler.ts b/src/dev/loadErrorMessageHandler.ts index 120f7d43919..6dc7743ef2c 100644 --- a/src/dev/loadErrorMessageHandler.ts +++ b/src/dev/loadErrorMessageHandler.ts @@ -1,6 +1,6 @@ -import type { ErrorCodes } from '../invariantErrorCodes.js'; -import { global } from '../utilities/globals/index.js'; -import { ApolloErrorMessageHandler } from '../utilities/globals/invariantWrappers.js'; +import type { ErrorCodes } from "../invariantErrorCodes.js"; +import { global } from "../utilities/globals/index.js"; +import { ApolloErrorMessageHandler } from "../utilities/globals/invariantWrappers.js"; export function loadErrorMessageHandler(...errorCodes: ErrorCodes[]) { if (!global[ApolloErrorMessageHandler]) { @@ -14,13 +14,13 @@ export function loadErrorMessageHandler(...errorCodes: ErrorCodes[]) { return global[ApolloErrorMessageHandler]; function handler(message: string | number, args: unknown[]) { - if (typeof message === 'number') { + if (typeof message === "number") { const definition = global[ApolloErrorMessageHandler]![message]; if (!message || !definition.message) return; message = definition.message; } return args.reduce( - (msg, arg) => msg.replace('%s', String(arg)), + (msg, arg) => msg.replace("%s", String(arg)), String(message) ); } diff --git a/src/dev/loadErrorMessages.ts b/src/dev/loadErrorMessages.ts index fa64ef81735..a2c08706362 100644 --- a/src/dev/loadErrorMessages.ts +++ b/src/dev/loadErrorMessages.ts @@ -1,5 +1,5 @@ -import { errorCodes } from '../invariantErrorCodes.js'; -import { loadErrorMessageHandler } from './loadErrorMessageHandler.js'; +import { errorCodes } from "../invariantErrorCodes.js"; +import { loadErrorMessageHandler } from "./loadErrorMessageHandler.js"; export function loadErrorMessages() { loadErrorMessageHandler(errorCodes); diff --git a/src/link/remove-typename/__tests__/removeTypenameFromVariables.ts b/src/link/remove-typename/__tests__/removeTypenameFromVariables.ts index e7f7aedf14a..b067e539cb0 100644 --- a/src/link/remove-typename/__tests__/removeTypenameFromVariables.ts +++ b/src/link/remove-typename/__tests__/removeTypenameFromVariables.ts @@ -1,13 +1,13 @@ import { KEEP, removeTypenameFromVariables, -} from '../removeTypenameFromVariables'; -import { ApolloLink, Operation } from '../../core'; -import { Observable, gql } from '../../../core'; -import { createOperation, toPromise } from '../../utils'; +} from "../removeTypenameFromVariables"; +import { ApolloLink, Operation } from "../../core"; +import { Observable, gql } from "../../../core"; +import { createOperation, toPromise } from "../../utils"; -type PartialOperation = Partial> & - Pick; +type PartialOperation = Partial> & + Pick; // Since this link modifies the `operation` and we only care to test against // the changed operation, we use a custom `execute` helper here instead of the @@ -26,7 +26,7 @@ async function execute(link: ApolloLink, operation: PartialOperation) { return data as Operation; } -test('strips all __typename keys by default', async () => { +test("strips all __typename keys by default", async () => { const query = gql` query Test($foo: FooInput!, $bar: BarInput!) { someField(foo: $foo, bar: $bar) @@ -39,20 +39,20 @@ test('strips all __typename keys by default', async () => { query, variables: { foo: { - __typename: 'Foo', + __typename: "Foo", foo: true, - bar: 'Bar', - baz: { __typename: 'Baz', baz: true }, - qux: [{ __typename: 'Qux', qux: 0 }], + bar: "Bar", + baz: { __typename: "Baz", baz: true }, + qux: [{ __typename: "Qux", qux: 0 }], }, - bar: [{ __typename: 'Bar', bar: true }], + bar: [{ __typename: "Bar", bar: true }], }, }); expect(variables).toStrictEqual({ foo: { foo: true, - bar: 'Bar', + bar: "Bar", baz: { baz: true }, qux: [{ qux: 0 }], }, @@ -60,7 +60,7 @@ test('strips all __typename keys by default', async () => { }); }); -test('does nothing when no variables are passed', async () => { +test("does nothing when no variables are passed", async () => { const query = gql` query Test { foo { @@ -77,7 +77,7 @@ test('does nothing when no variables are passed', async () => { expect(resultOperation).toBe(operation); }); -test('does nothing when no variables are passed even if variables are declared in the document', async () => { +test("does nothing when no variables are passed even if variables are declared in the document", async () => { const query = gql` query Test($unused: Boolean) { foo { @@ -94,7 +94,7 @@ test('does nothing when no variables are passed even if variables are declared i expect(resultOperation).toBe(operation); }); -test('keeps __typename for variables with types defined by `except`', async () => { +test("keeps __typename for variables with types defined by `except`", async () => { const query = gql` query Test($foo: JSON, $bar: BarInput) { someField(foo: $foo, bar: $bar) @@ -111,25 +111,25 @@ test('keeps __typename for variables with types defined by `except`', async () = query, variables: { foo: { - __typename: 'Foo', + __typename: "Foo", foo: true, - baz: { __typename: 'Baz', baz: true }, + baz: { __typename: "Baz", baz: true }, }, - bar: { __typename: 'Bar', bar: true }, + bar: { __typename: "Bar", bar: true }, }, }); expect(variables).toStrictEqual({ foo: { - __typename: 'Foo', + __typename: "Foo", foo: true, - baz: { __typename: 'Baz', baz: true }, + baz: { __typename: "Baz", baz: true }, }, bar: { bar: true }, }); }); -test('keeps __typename in all variables with types configured with `except`', async () => { +test("keeps __typename in all variables with types configured with `except`", async () => { const query = gql` query Test($foo: JSON, $bar: Config, $baz: BazInput) { someField(foo: $foo, bar: $bar, baz: $baz) @@ -146,20 +146,20 @@ test('keeps __typename in all variables with types configured with `except`', as const { variables } = await execute(link, { query, variables: { - foo: { __typename: 'Foo', foo: true }, - bar: { __typename: 'Bar', bar: true }, - baz: { __typename: 'Baz', baz: true }, + foo: { __typename: "Foo", foo: true }, + bar: { __typename: "Bar", bar: true }, + baz: { __typename: "Baz", baz: true }, }, }); expect(variables).toStrictEqual({ - foo: { __typename: 'Foo', foo: true }, - bar: { __typename: 'Bar', bar: true }, + foo: { __typename: "Foo", foo: true }, + bar: { __typename: "Bar", bar: true }, baz: { baz: true }, }); }); -test('handles variable declarations declared as non null and list types', async () => { +test("handles variable declarations declared as non null and list types", async () => { const query = gql` query Test($foo: JSON!, $bar: [JSON], $baz: [JSON!]!, $qux: QuxInput!) { someField(foo: $foo, bar: $bar, baz: $baz) @@ -175,32 +175,32 @@ test('handles variable declarations declared as non null and list types', async const { variables } = await execute(link, { query, variables: { - foo: { __typename: 'Foo', foo: true }, + foo: { __typename: "Foo", foo: true }, bar: [ - { __typename: 'Bar', bar: true, baz: { __typename: 'Baz', baz: true } }, + { __typename: "Bar", bar: true, baz: { __typename: "Baz", baz: true } }, ], baz: [ - { __typename: 'Baz', baz: true }, - { __typename: 'Baz', baz: true }, + { __typename: "Baz", baz: true }, + { __typename: "Baz", baz: true }, ], - qux: { __typename: 'Qux', qux: true }, + qux: { __typename: "Qux", qux: true }, }, }); expect(variables).toStrictEqual({ - foo: { __typename: 'Foo', foo: true }, + foo: { __typename: "Foo", foo: true }, bar: [ - { __typename: 'Bar', bar: true, baz: { __typename: 'Baz', baz: true } }, + { __typename: "Bar", bar: true, baz: { __typename: "Baz", baz: true } }, ], baz: [ - { __typename: 'Baz', baz: true }, - { __typename: 'Baz', baz: true }, + { __typename: "Baz", baz: true }, + { __typename: "Baz", baz: true }, ], qux: { qux: true }, }); }); -test('keeps __typename at configured fields under input object types', async () => { +test("keeps __typename at configured fields under input object types", async () => { const query = gql` query Test($foo: FooInput) { someField(foo: $foo) @@ -220,18 +220,18 @@ test('keeps __typename at configured fields under input object types', async () query, variables: { foo: { - __typename: 'Foo', + __typename: "Foo", aa: true, bar: { - __typename: 'Bar', + __typename: "Bar", bb: true, }, baz: { - __typename: 'Baz', + __typename: "Baz", cc: true, }, qux: { - __typename: 'Qux', + __typename: "Qux", dd: true, }, }, @@ -242,11 +242,11 @@ test('keeps __typename at configured fields under input object types', async () foo: { aa: true, bar: { - __typename: 'Bar', + __typename: "Bar", bb: true, }, baz: { - __typename: 'Baz', + __typename: "Baz", cc: true, }, qux: { @@ -256,7 +256,7 @@ test('keeps __typename at configured fields under input object types', async () }); }); -test('keeps __typename at a deeply nested field', async () => { +test("keeps __typename at a deeply nested field", async () => { const query = gql` query Test($foo: FooInput) { someField(foo: $foo) @@ -279,13 +279,13 @@ test('keeps __typename at a deeply nested field', async () => { query, variables: { foo: { - __typename: 'Foo', + __typename: "Foo", bar: { - __typename: 'Bar', + __typename: "Bar", baz: { - __typename: 'Baz', + __typename: "Baz", qux: { - __typename: 'Qux', + __typename: "Qux", quux: true, }, }, @@ -299,7 +299,7 @@ test('keeps __typename at a deeply nested field', async () => { bar: { baz: { qux: { - __typename: 'Qux', + __typename: "Qux", quux: true, }, }, @@ -308,7 +308,7 @@ test('keeps __typename at a deeply nested field', async () => { }); }); -test('handles configured fields varying nesting levels', async () => { +test("handles configured fields varying nesting levels", async () => { const query = gql` query Test($foo: FooInput) { someField(foo: $foo) @@ -330,15 +330,15 @@ test('handles configured fields varying nesting levels', async () => { query, variables: { foo: { - __typename: 'Foo', + __typename: "Foo", bar: { - __typename: 'Bar', + __typename: "Bar", aa: true, }, baz: { - __typename: 'Baz', + __typename: "Baz", qux: { - __typename: 'Qux', + __typename: "Qux", quux: true, }, }, @@ -349,12 +349,12 @@ test('handles configured fields varying nesting levels', async () => { expect(variables).toStrictEqual({ foo: { bar: { - __typename: 'Bar', + __typename: "Bar", aa: true, }, baz: { qux: { - __typename: 'Qux', + __typename: "Qux", quux: true, }, }, @@ -362,7 +362,7 @@ test('handles configured fields varying nesting levels', async () => { }); }); -test('handles multiple configured types with fields', async () => { +test("handles multiple configured types with fields", async () => { const query = gql` query Test($foo: FooInput, $baz: BazInput) { someField(foo: $foo, baz: $baz) @@ -384,16 +384,16 @@ test('handles multiple configured types with fields', async () => { query, variables: { foo: { - __typename: 'Foo', + __typename: "Foo", bar: { - __typename: 'Bar', + __typename: "Bar", aa: true, }, }, baz: { - __typename: 'Bar', + __typename: "Bar", qux: { - __typename: 'Qux', + __typename: "Qux", bb: true, }, }, @@ -403,20 +403,20 @@ test('handles multiple configured types with fields', async () => { expect(variables).toStrictEqual({ foo: { bar: { - __typename: 'Bar', + __typename: "Bar", aa: true, }, }, baz: { qux: { - __typename: 'Qux', + __typename: "Qux", bb: true, }, }, }); }); -test('handles when __typename is not present in all paths', async () => { +test("handles when __typename is not present in all paths", async () => { const query = gql` query Test($foo: JSON, $bar: BarInput) { someField(foo: $foo, bar: $bar) @@ -434,24 +434,24 @@ test('handles when __typename is not present in all paths', async () => { variables: { foo: { foo: true, - baz: { __typename: 'Baz', baz: true }, + baz: { __typename: "Baz", baz: true }, }, bar: { bar: true }, - qux: { __typename: 'Qux', bar: true }, + qux: { __typename: "Qux", bar: true }, }, }); expect(variables).toStrictEqual({ foo: { foo: true, - baz: { __typename: 'Baz', baz: true }, + baz: { __typename: "Baz", baz: true }, }, bar: { bar: true }, qux: { bar: true }, }); }); -test('handles when __typename is not present in variables', async () => { +test("handles when __typename is not present in variables", async () => { const query = gql` query Test($foo: JSON, $bar: BarInput) { someField(foo: $foo, bar: $bar) @@ -486,7 +486,7 @@ test('handles when __typename is not present in variables', async () => { }); }); -test('handles when declared variables are unused', async () => { +test("handles when declared variables are unused", async () => { const query = gql` query Test($foo: FooInput, $unused: JSON) { someField(foo: $foo, bar: $bar) @@ -503,9 +503,9 @@ test('handles when declared variables are unused', async () => { query, variables: { foo: { - __typename: 'Foo', + __typename: "Foo", foo: true, - baz: { __typename: 'Bar', baz: true }, + baz: { __typename: "Bar", baz: true }, }, }, }); @@ -518,7 +518,7 @@ test('handles when declared variables are unused', async () => { }); }); -test('ensures operation.getContext and operation.setContext functions are properly forwarded', async () => { +test("ensures operation.getContext and operation.setContext functions are properly forwarded", async () => { const query = gql` query Test($foo: FooInput) { someField(foo: $foo) @@ -530,11 +530,11 @@ test('ensures operation.getContext and operation.setContext functions are proper const operationWithoutVariables = await execute(link, { query }); const operationWithVariables = await execute(link, { query, - variables: { foo: { __typename: 'FooInput', bar: true } }, + variables: { foo: { __typename: "FooInput", bar: true } }, }); - expect(typeof operationWithoutVariables.getContext).toBe('function'); - expect(typeof operationWithoutVariables.setContext).toBe('function'); - expect(typeof operationWithVariables.getContext).toBe('function'); - expect(typeof operationWithVariables.setContext).toBe('function'); + expect(typeof operationWithoutVariables.getContext).toBe("function"); + expect(typeof operationWithoutVariables.setContext).toBe("function"); + expect(typeof operationWithVariables.getContext).toBe("function"); + expect(typeof operationWithVariables.setContext).toBe("function"); }); diff --git a/src/link/remove-typename/index.ts b/src/link/remove-typename/index.ts index e84f627bf7a..372216a8475 100644 --- a/src/link/remove-typename/index.ts +++ b/src/link/remove-typename/index.ts @@ -2,4 +2,4 @@ export { removeTypenameFromVariables, KEEP, RemoveTypenameFromVariablesOptions, -} from './removeTypenameFromVariables.js'; +} from "./removeTypenameFromVariables.js"; diff --git a/src/link/remove-typename/removeTypenameFromVariables.ts b/src/link/remove-typename/removeTypenameFromVariables.ts index 50a79c64dd6..f8362dddbc3 100644 --- a/src/link/remove-typename/removeTypenameFromVariables.ts +++ b/src/link/remove-typename/removeTypenameFromVariables.ts @@ -1,11 +1,11 @@ -import { wrap } from 'optimism'; -import type { DocumentNode, TypeNode } from 'graphql'; -import { Kind, visit } from 'graphql'; -import { ApolloLink } from '../core/index.js'; -import { stripTypename, isPlainObject } from '../../utilities/index.js'; -import type { OperationVariables } from '../../core/index.js'; +import { wrap } from "optimism"; +import type { DocumentNode, TypeNode } from "graphql"; +import { Kind, visit } from "graphql"; +import { ApolloLink } from "../core/index.js"; +import { stripTypename, isPlainObject } from "../../utilities/index.js"; +import type { OperationVariables } from "../../core/index.js"; -export const KEEP = '__KEEP'; +export const KEEP = "__KEEP"; interface KeepTypenameConfig { [key: string]: typeof KEEP | KeepTypenameConfig; @@ -75,7 +75,7 @@ function maybeStripTypename( Object.keys(value).forEach((key) => { const child = value[key]; - if (key === '__typename') { + if (key === "__typename") { return; } diff --git a/src/react/cache/QueryReference.ts b/src/react/cache/QueryReference.ts index 36b9a94a67a..7951771ead3 100644 --- a/src/react/cache/QueryReference.ts +++ b/src/react/cache/QueryReference.ts @@ -1,24 +1,24 @@ -import { equal } from '@wry/equality'; +import { equal } from "@wry/equality"; import type { ApolloError, ApolloQueryResult, ObservableQuery, OperationVariables, WatchQueryOptions, -} from '../../core/index.js'; -import { isNetworkRequestSettled } from '../../core/index.js'; -import type { ObservableSubscription } from '../../utilities/index.js'; +} from "../../core/index.js"; +import { isNetworkRequestSettled } from "../../core/index.js"; +import type { ObservableSubscription } from "../../utilities/index.js"; import { createFulfilledPromise, createRejectedPromise, -} from '../../utilities/index.js'; -import type { CacheKey } from './types.js'; -import type { useBackgroundQuery, useReadQuery } from '../hooks/index.js'; +} from "../../utilities/index.js"; +import type { CacheKey } from "./types.js"; +import type { useBackgroundQuery, useReadQuery } from "../hooks/index.js"; type Listener = (promise: Promise>) => void; type FetchMoreOptions = Parameters< - ObservableQuery['fetchMore'] + ObservableQuery["fetchMore"] >[0]; export const QUERY_REFERENCE_SYMBOL: unique symbol = Symbol(); @@ -38,12 +38,12 @@ interface InternalQueryReferenceOptions { } const OBSERVED_CHANGED_OPTIONS: Array = [ - 'canonizeResults', - 'context', - 'errorPolicy', - 'fetchPolicy', - 'refetchWritePolicy', - 'returnPartialData', + "canonizeResults", + "context", + "errorPolicy", + "fetchPolicy", + "refetchWritePolicy", + "returnPartialData", ]; export class InternalQueryReference { @@ -57,7 +57,7 @@ export class InternalQueryReference { private subscription: ObservableSubscription; private listeners = new Set>(); private autoDisposeTimeoutId: NodeJS.Timeout; - private status: 'idle' | 'loading' = 'loading'; + private status: "idle" | "loading" = "loading"; private resolve: ((result: ApolloQueryResult) => void) | undefined; private reject: ((error: unknown) => void) | undefined; @@ -87,7 +87,7 @@ export class InternalQueryReference { (!this.result.partial || this.watchQueryOptions.returnPartialData)) ) { this.promise = createFulfilledPromise(this.result); - this.status = 'idle'; + this.status = "idle"; } else { this.promise = new Promise((resolve, reject) => { this.resolve = resolve; @@ -154,7 +154,7 @@ export class InternalQueryReference { // "standby" is used when `skip` is set to `true`. Detect when we've // enabled the query (i.e. `skip` is `false`) to execute a network request. if ( - currentFetchPolicy === 'standby' && + currentFetchPolicy === "standby" && currentFetchPolicy !== watchQueryOptions.fetchPolicy ) { this.initiateFetch(this.observable.reobserve(watchQueryOptions)); @@ -197,18 +197,18 @@ export class InternalQueryReference { private handleNext(result: ApolloQueryResult) { switch (this.status) { - case 'loading': { + case "loading": { // Maintain the last successful `data` value if the next result does not // have one. if (result.data === void 0) { result.data = this.result.data; } - this.status = 'idle'; + this.status = "idle"; this.result = result; this.resolve?.(result); break; } - case 'idle': { + case "idle": { if (result.data === this.result.data) { return; } @@ -229,12 +229,12 @@ export class InternalQueryReference { private handleError(error: ApolloError) { switch (this.status) { - case 'loading': { - this.status = 'idle'; + case "loading": { + this.status = "idle"; this.reject?.(error); break; } - case 'idle': { + case "idle": { this.promise = createRejectedPromise(error); this.deliver(this.promise); } @@ -246,7 +246,7 @@ export class InternalQueryReference { } private initiateFetch(returnedPromise: Promise>) { - this.status = 'loading'; + this.status = "loading"; this.promise = new Promise((resolve, reject) => { this.resolve = resolve; @@ -262,8 +262,8 @@ export class InternalQueryReference { // promise is resolved correctly. returnedPromise .then((result) => { - if (this.status === 'loading') { - this.status = 'idle'; + if (this.status === "loading") { + this.status = "idle"; this.result = result; this.resolve?.(result); } diff --git a/src/react/cache/SuspenseCache.ts b/src/react/cache/SuspenseCache.ts index b5a3b05ba6f..e5f8036ecea 100644 --- a/src/react/cache/SuspenseCache.ts +++ b/src/react/cache/SuspenseCache.ts @@ -1,8 +1,8 @@ -import { Trie } from '@wry/trie'; -import type { ObservableQuery } from '../../core/index.js'; -import { canUseWeakMap } from '../../utilities/index.js'; -import { InternalQueryReference } from './QueryReference.js'; -import type { CacheKey } from './types.js'; +import { Trie } from "@wry/trie"; +import type { ObservableQuery } from "../../core/index.js"; +import { canUseWeakMap } from "../../utilities/index.js"; +import { InternalQueryReference } from "./QueryReference.js"; +import type { CacheKey } from "./types.js"; export interface SuspenseCacheOptions { /** @@ -19,7 +19,9 @@ export interface SuspenseCacheOptions { } export class SuspenseCache { - private queryRefs = new Trie<{ current?: InternalQueryReference }>(canUseWeakMap); + private queryRefs = new Trie<{ current?: InternalQueryReference }>( + canUseWeakMap + ); private options: SuspenseCacheOptions; constructor(options: SuspenseCacheOptions = Object.create(null)) { diff --git a/src/react/cache/getSuspenseCache.ts b/src/react/cache/getSuspenseCache.ts index b696a5366f7..93fbc72b98a 100644 --- a/src/react/cache/getSuspenseCache.ts +++ b/src/react/cache/getSuspenseCache.ts @@ -1,8 +1,8 @@ -import type { SuspenseCacheOptions } from './index.js'; -import { SuspenseCache } from './SuspenseCache.js'; -import type { ApolloClient } from '../../core/ApolloClient.js'; +import type { SuspenseCacheOptions } from "./index.js"; +import { SuspenseCache } from "./SuspenseCache.js"; +import type { ApolloClient } from "../../core/ApolloClient.js"; -declare module '../../core/ApolloClient.js' { +declare module "../../core/ApolloClient.js" { interface DefaultOptions { react?: { suspense?: Readonly; @@ -10,7 +10,7 @@ declare module '../../core/ApolloClient.js' { } } -const suspenseCacheSymbol = Symbol.for('apollo.suspenseCache'); +const suspenseCacheSymbol = Symbol.for("apollo.suspenseCache"); export function getSuspenseCache( client: ApolloClient & { diff --git a/src/react/cache/index.ts b/src/react/cache/index.ts index 1ed99e6512f..95a1a1c9c86 100644 --- a/src/react/cache/index.ts +++ b/src/react/cache/index.ts @@ -1,27 +1,27 @@ -export type { SuspenseCacheOptions } from './SuspenseCache.js'; -export { getSuspenseCache } from './getSuspenseCache.js'; +export type { SuspenseCacheOptions } from "./SuspenseCache.js"; +export { getSuspenseCache } from "./getSuspenseCache.js"; -import { SuspenseCache as RealSuspenseCache } from './SuspenseCache.js'; +import { SuspenseCache as RealSuspenseCache } from "./SuspenseCache.js"; // TODO: remove export with release 3.8 // replace with // export type { SuspenseCache } from './SuspenseCache.js'; /** - * @deprecated + * @deprecated * It is no longer necessary to create a `SuspenseCache` instance and pass it into the `ApolloProvider`. * Please remove this code from your application. - * + * * This export will be removed with the final 3.8 release. */ export class SuspenseCache extends RealSuspenseCache { - constructor(){ + constructor() { super(); // throwing an error here instead of using invariant - we do not want this error // message to be link-ified, but to directly show up as bold as possible throw new Error( - 'It is no longer necessary to create a `SuspenseCache` instance and pass it into the `ApolloProvider`.\n' + - 'Please remove this code from your application. \n\n' + - 'This export will be removed with the final 3.8 release.' + "It is no longer necessary to create a `SuspenseCache` instance and pass it into the `ApolloProvider`.\n" + + "Please remove this code from your application. \n\n" + + "This export will be removed with the final 3.8 release." ); } -} \ No newline at end of file +} diff --git a/src/react/cache/types.ts b/src/react/cache/types.ts index 65b75ec8704..e9e0ba8b826 100644 --- a/src/react/cache/types.ts +++ b/src/react/cache/types.ts @@ -1,3 +1,7 @@ import type { DocumentNode } from "graphql"; -export type CacheKey = [query: DocumentNode, stringifiedVariables: string, ...queryKey: any[]]; +export type CacheKey = [ + query: DocumentNode, + stringifiedVariables: string, + ...queryKey: any[], +]; diff --git a/src/react/components/Mutation.tsx b/src/react/components/Mutation.tsx index 96a150be61d..492e0089312 100644 --- a/src/react/components/Mutation.tsx +++ b/src/react/components/Mutation.tsx @@ -1,8 +1,8 @@ -import * as PropTypes from 'prop-types'; +import * as PropTypes from "prop-types"; -import type { OperationVariables } from '../../core/index.js'; -import type { MutationComponentOptions } from './types.js'; -import { useMutation } from '../hooks/index.js'; +import type { OperationVariables } from "../../core/index.js"; +import type { MutationComponentOptions } from "./types.js"; +import { useMutation } from "../hooks/index.js"; export function Mutation( props: MutationComponentOptions @@ -23,7 +23,7 @@ Mutation.propTypes = { PropTypes.arrayOf( PropTypes.oneOfType([PropTypes.string, PropTypes.object]) ), - PropTypes.func + PropTypes.func, ]), awaitRefetchQueries: PropTypes.bool, update: PropTypes.func, diff --git a/src/react/components/Query.tsx b/src/react/components/Query.tsx index 7332577e2bf..1e5611f646a 100644 --- a/src/react/components/Query.tsx +++ b/src/react/components/Query.tsx @@ -1,12 +1,13 @@ -import * as PropTypes from 'prop-types'; +import * as PropTypes from "prop-types"; -import type { OperationVariables } from '../../core/index.js'; -import type { QueryComponentOptions } from './types.js'; -import { useQuery } from '../hooks/index.js'; +import type { OperationVariables } from "../../core/index.js"; +import type { QueryComponentOptions } from "./types.js"; +import { useQuery } from "../hooks/index.js"; -export function Query( - props: QueryComponentOptions -) { +export function Query< + TData = any, + TVariables extends OperationVariables = OperationVariables, +>(props: QueryComponentOptions) { const { children, query, ...options } = props; const result = useQuery(query, options); return result ? children(result as any) : null; @@ -28,5 +29,5 @@ Query.propTypes = { variables: PropTypes.object, ssr: PropTypes.bool, partialRefetch: PropTypes.bool, - returnPartialData: PropTypes.bool + returnPartialData: PropTypes.bool, } as Query["propTypes"]; diff --git a/src/react/components/Subscription.tsx b/src/react/components/Subscription.tsx index fe262391bd8..5701cdcb01d 100644 --- a/src/react/components/Subscription.tsx +++ b/src/react/components/Subscription.tsx @@ -1,18 +1,21 @@ -import * as PropTypes from 'prop-types'; +import * as PropTypes from "prop-types"; -import type { OperationVariables } from '../../core/index.js'; -import type { SubscriptionComponentOptions } from './types.js'; -import { useSubscription } from '../hooks/index.js'; +import type { OperationVariables } from "../../core/index.js"; +import type { SubscriptionComponentOptions } from "./types.js"; +import { useSubscription } from "../hooks/index.js"; -export function Subscription( - props: SubscriptionComponentOptions -) { +export function Subscription< + TData = any, + TVariables extends OperationVariables = OperationVariables, +>(props: SubscriptionComponentOptions) { const result = useSubscription(props.subscription, props); return props.children && result ? props.children(result) : null; } export interface Subscription { - propTypes: PropTypes.InferProps>; + propTypes: PropTypes.InferProps< + SubscriptionComponentOptions + >; } Subscription.propTypes = { @@ -23,5 +26,5 @@ Subscription.propTypes = { onData: PropTypes.func, onSubscriptionComplete: PropTypes.func, onComplete: PropTypes.func, - shouldResubscribe: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]) + 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 index e0e08f83e4f..80c75f99506 100644 --- a/src/react/components/__tests__/client/Mutation.test.tsx +++ b/src/react/components/__tests__/client/Mutation.test.tsx @@ -1,21 +1,21 @@ -import React, { useState, PropsWithChildren } from 'react'; -import gql from 'graphql-tag'; -import { ExecutionResult, 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 React, { useState, PropsWithChildren } from "react"; +import gql from "graphql-tag"; +import { ExecutionResult, 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'; +} from "../../../../testing"; +import { Query } from "../../Query"; +import { Mutation } from "../../Mutation"; const mutation = gql` mutation createTodo($text: String!) { @@ -41,79 +41,79 @@ type Data = { const data: Data = { createTodo: { - __typename: 'Todo', - id: '99', - text: 'This one was created with a mutation.', - completed: true + __typename: "Todo", + id: "99", + text: "This one was created with a mutation.", + completed: true, }, - __typename: 'Mutation' + __typename: "Mutation", }; const data2: Data = { createTodo: { - __typename: 'Todo', - id: '100', - text: 'This one was created with a mutation.', - completed: true + __typename: "Todo", + id: "100", + text: "This one was created with a mutation.", + completed: true, }, - __typename: 'Mutation' + __typename: "Mutation", }; const mocks = [ { request: { query: mutation }, - result: { data } + result: { data }, }, { request: { query: mutation }, - result: { data: data2 } - } + result: { data: data2 }, + }, ]; const cache = new Cache({ addTypename: false }); -describe('General Mutation testing', () => { - it('pick prop client over context client', async () => { +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', + __typename: "Todo", + id: "99", text, - completed: true + completed: true, }, - __typename: 'Mutation' - } - } + __typename: "Mutation", + }, + }, }, { request: { query: mutation }, result: { data: { createTodo: { - __typename: 'Todo', - id: '100', + __typename: "Todo", + id: "100", text, - completed: true + completed: true, }, - __typename: 'Mutation' - } - } - } + __typename: "Mutation", + }, + }, + }, ]; - const mocksProps = mock('This is the result of the prop client 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.' + "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 }) + cache: new Cache({ addTypename: false }), }); } @@ -134,48 +134,66 @@ describe('General Mutation testing', () => { }; const { rerender } = render(); - await waitFor(() => { - screen.getByText('Create'); - }, { interval: 1 }); + await waitFor( + () => { + screen.getByText("Create"); + }, + { interval: 1 } + ); // context client mutation - await userEvent.click(screen.getByText('Create')); + await userEvent.click(screen.getByText("Create")); - await waitFor(() => { - expect(spy).toHaveBeenCalledWith(mocksContext[0].result); - }, { interval: 1 }); + await waitFor( + () => { + expect(spy).toHaveBeenCalledWith(mocksContext[0].result); + }, + { interval: 1 } + ); // props client mutation rerender(); - await userEvent.click(screen.getByText('Create')); + await userEvent.click(screen.getByText("Create")); - await waitFor(() => { - expect(spy).toHaveBeenCalledWith(mocksProps[0].result); - }, { interval: 1 }); + await waitFor( + () => { + expect(spy).toHaveBeenCalledWith(mocksProps[0].result); + }, + { interval: 1 } + ); // context client mutation rerender(); - await userEvent.click(screen.getByText('Create')); + await userEvent.click(screen.getByText("Create")); - await waitFor(() => { - expect(spy).toHaveBeenCalledWith(mocksContext[1].result); - }, { interval: 1 }); + await waitFor( + () => { + expect(spy).toHaveBeenCalledWith(mocksContext[1].result); + }, + { interval: 1 } + ); // props client mutation rerender(); - await userEvent.click(screen.getByText('Create')); + await userEvent.click(screen.getByText("Create")); - await waitFor(() => { - expect(spy).toHaveBeenCalledWith(mocksProps[1].result); - }, { interval: 1 }); + await waitFor( + () => { + expect(spy).toHaveBeenCalledWith(mocksProps[1].result); + }, + { interval: 1 } + ); - await waitFor(() => { - expect(spy).toHaveBeenCalledTimes(4); - }, { interval: 1 }); + await waitFor( + () => { + expect(spy).toHaveBeenCalledTimes(4); + }, + { interval: 1 } + ); }); - itAsync('performs a mutation', (resolve, reject) => { + itAsync("performs a mutation", (resolve, reject) => { let count = 0; const Component = () => ( @@ -213,37 +231,40 @@ describe('General Mutation testing', () => { }).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(); + 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
; - }} - - ); + } else if (count === 1) { + reject("rerender happened with ignoreResults turned on"); + } + count++; + return
; + }} + + ); - render( - - - - ); - }); + render( + + + + ); + } + ); - it('returns a resolved promise when calling the mutation function', async () => { + it("returns a resolved promise when calling the mutation function", async () => { let called = false; let result: any; const Component = () => ( @@ -251,7 +272,7 @@ describe('General Mutation testing', () => { {(createTodo: any) => { if (!called) { createTodo().then((_result: any) => { - result = _result + result = _result; }); } called = true; @@ -269,10 +290,10 @@ describe('General Mutation testing', () => { await waitFor(() => { expect(result!.data).toEqual(data); - }) + }); }); - it('returns rejected promise when calling the mutation function', async () => { + it("returns rejected promise when calling the mutation function", async () => { let done = false; let called = false; const Component = () => ( @@ -280,7 +301,7 @@ describe('General Mutation testing', () => { {(createTodo: any) => { if (!called) { createTodo().catch((error: any) => { - expect(error).toEqual(new Error('Error 1')); + expect(error).toEqual(new Error("Error 1")); done = true; }); } @@ -294,8 +315,8 @@ describe('General Mutation testing', () => { const mocksWithErrors = [ { request: { query: mutation }, - error: new Error('Error 1') - } + error: new Error("Error 1"), + }, ]; render( @@ -309,7 +330,7 @@ describe('General Mutation testing', () => { }); }); - it('only shows result for the latest mutation that is in flight', async () => { + it("only shows result for the latest mutation that is in flight", async () => { let count = 0; const onCompleted = (dataMutation: Data) => { @@ -352,14 +373,14 @@ describe('General Mutation testing', () => { }); }); - it('only shows the error for the latest mutation in flight', async () => { + 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 Error('Error 1')); + expect(error).toEqual(new Error("Error 1")); } else if (count === 3) { - expect(error).toEqual(new Error('Error 2')); + expect(error).toEqual(new Error("Error 2")); } }; const Component = () => ( @@ -377,7 +398,7 @@ describe('General Mutation testing', () => { expect(result.loading).toEqual(false); expect(result.data).toEqual(undefined); expect(result.called).toEqual(true); - expect(result.error).toEqual(new Error('Error 2')); + expect(result.error).toEqual(new Error("Error 2")); } count++; return
; @@ -388,12 +409,12 @@ describe('General Mutation testing', () => { const mocksWithErrors = [ { request: { query: mutation }, - error: new Error('Error 2') + error: new Error("Error 2"), }, { request: { query: mutation }, - error: new Error('Error 2') - } + error: new Error("Error 2"), + }, ]; render( @@ -407,19 +428,19 @@ describe('General Mutation testing', () => { }); }); - it('calls the onCompleted prop as soon as the mutation is complete', async () => { + it("calls the onCompleted prop as soon as the mutation is complete", async () => { let onCompletedCalled = false; class Component extends React.Component { state = { - mutationDone: false + mutationDone: false, }; onCompleted = (mutationData: Data) => { expect(mutationData).toEqual(data); onCompletedCalled = true; this.setState({ - mutationDone: true + mutationDone: true, }); }; @@ -448,11 +469,11 @@ describe('General Mutation testing', () => { ); await waitFor(() => { - expect(onCompletedCalled).toEqual(true) + expect(onCompletedCalled).toEqual(true); }); }); - it('renders result of the children render prop', () => { + it("renders result of the children render prop", () => { const Component = () => ( {() =>
result
}
); @@ -462,26 +483,24 @@ describe('General Mutation testing', () => { ); - expect(screen.getByText('result')).toBeTruthy(); + 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() + unmount(); }); - it('renders an error state', async () => { + 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 Error('error occurred')); + expect(err).toEqual(new Error("error occurred")); }); } else if (count === 1) { expect(result.loading).toBeTruthy(); } else if (count === 2) { - expect(result.error).toEqual( - new Error('error occurred') - ); + expect(result.error).toEqual(new Error("error occurred")); } count++; return
; @@ -492,8 +511,8 @@ describe('General Mutation testing', () => { const mockError = [ { request: { query: mutation }, - error: new Error('error occurred') - } + error: new Error("error occurred"), + }, ]; render( @@ -507,11 +526,11 @@ describe('General Mutation testing', () => { }); }); - it('renders an error state and throws when encountering graphql errors', async () => { + it("renders an error state and throws when encountering graphql errors", async () => { let count = 0; const expectedError = new ApolloError({ - graphQLErrors: [new GraphQLError('error occurred')] + graphQLErrors: [new GraphQLError("error occurred")], }); const Component = () => ( @@ -520,7 +539,7 @@ describe('General Mutation testing', () => { if (count === 0) { createTodo() .then(() => { - throw new Error('Did not expect a result'); + throw new Error("Did not expect a result"); }) .catch((e: any) => { expect(e).toEqual(expectedError); @@ -540,9 +559,9 @@ describe('General Mutation testing', () => { { request: { query: mutation }, result: { - errors: [new GraphQLError('error occurred')] - } - } + errors: [new GraphQLError("error occurred")], + }, + }, ]; render( @@ -556,7 +575,7 @@ describe('General Mutation testing', () => { }); }); - it('renders an error state and does not throw when encountering graphql errors when errorPolicy=all', async () => { + it("renders an error state and does not throw when encountering graphql errors when errorPolicy=all", async () => { let count = 0; const Component = () => ( @@ -567,7 +586,7 @@ describe('General Mutation testing', () => { if (fetchResult && fetchResult.errors) { expect(fetchResult.errors.length).toEqual(1); expect(fetchResult.errors[0]).toEqual( - new GraphQLError('error occurred') + new GraphQLError("error occurred") ); } else { throw new Error( @@ -583,7 +602,7 @@ describe('General Mutation testing', () => { } else if (count === 2) { expect(result.error).toEqual( new ApolloError({ - graphQLErrors: [new GraphQLError('error occurred')] + graphQLErrors: [new GraphQLError("error occurred")], }) ); } @@ -597,14 +616,14 @@ describe('General Mutation testing', () => { { request: { query: mutation }, result: { - errors: [new GraphQLError('error occurred')] - } - } + errors: [new GraphQLError("error occurred")], + }, + }, ]; render( @@ -616,10 +635,10 @@ describe('General Mutation testing', () => { }); }); - it('renders an error state and throws when encountering network errors when errorPolicy=all', async () => { + 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') + networkError: new Error("network error"), }); const Component = () => ( @@ -627,7 +646,7 @@ describe('General Mutation testing', () => { if (count === 0) { createTodo() .then(() => { - throw new Error('Did not expect a result'); + throw new Error("Did not expect a result"); }) .catch((e: any) => { expect(e).toEqual(expectedError); @@ -646,13 +665,13 @@ describe('General Mutation testing', () => { const mockError = [ { request: { query: mutation }, - error: new Error('network error') - } + error: new Error("network error"), + }, ]; render( @@ -664,16 +683,16 @@ describe('General Mutation testing', () => { }); }); - it('calls the onError prop if the mutation encounters an error', async () => { + it("calls the onError prop if the mutation encounters an error", async () => { let onRenderCalled = false; class Component extends React.Component { state = { - mutationError: false + mutationError: false, }; onError = (error: Error) => { - expect(error.message).toMatch('error occurred'); + expect(error.message).toMatch("error occurred"); onRenderCalled = true; this.setState({ mutationError: true }); }; @@ -701,8 +720,8 @@ describe('General Mutation testing', () => { const mockError = [ { request: { query: mutation }, - error: new Error('error occurred') - } + error: new Error("error occurred"), + }, ]; render( @@ -716,9 +735,9 @@ describe('General Mutation testing', () => { }); }); - it('performs a mutation with variables prop', async () => { + it("performs a mutation with variables prop", async () => { const variables = { - text: 'play tennis' + text: "play tennis", }; let count = 0; @@ -744,8 +763,8 @@ describe('General Mutation testing', () => { const mocks1 = [ { request: { query: mutation, variables }, - result: { data } - } + result: { data }, + }, ]; render( @@ -759,9 +778,9 @@ describe('General Mutation testing', () => { }); }); - it('allows passing a variable to the mutate function', async () => { + it("allows passing a variable to the mutate function", async () => { const variables = { - text: 'play tennis' + text: "play tennis", }; let count = 0; @@ -787,8 +806,8 @@ describe('General Mutation testing', () => { const mocks1 = [ { request: { query: mutation, variables }, - result: { data } - } + result: { data }, + }, ]; render( @@ -802,21 +821,21 @@ describe('General Mutation testing', () => { }); }); - it('allows an optimistic response prop', async () => { + it("allows an optimistic response prop", async () => { const link = mockSingleLink(...mocks); const client = new ApolloClient({ link, - cache + cache, }); const optimisticResponse = { createTodo: { - id: '99', - text: 'This is an optimistic response', + id: "99", + text: "This is an optimistic response", completed: false, - __typename: 'Todo' + __typename: "Todo", }, - __typename: 'Mutation' + __typename: "Mutation", }; let count = 0; @@ -826,7 +845,7 @@ describe('General Mutation testing', () => { if (count === 0) { createTodo(); const dataInStore = client.cache.extract(true); - expect(dataInStore['Todo:99']).toEqual( + expect(dataInStore["Todo:99"]).toEqual( optimisticResponse.createTodo ); } else if (count === 1) { @@ -854,21 +873,21 @@ describe('General Mutation testing', () => { }); }); - it('allows passing an optimistic response to the mutate function', async () => { + it("allows passing an optimistic response to the mutate function", async () => { const link = mockSingleLink(...mocks); const client = new ApolloClient({ link, - cache + cache, }); const optimisticResponse = { createTodo: { - id: '99', - text: 'This is an optimistic response', + id: "99", + text: "This is an optimistic response", completed: false, - __typename: 'Todo' + __typename: "Todo", }, - __typename: 'Mutation' + __typename: "Mutation", }; let count = 0; @@ -878,7 +897,7 @@ describe('General Mutation testing', () => { if (count === 0) { createTodo({ optimisticResponse }); const dataInStore = client.cache.extract(true); - expect(dataInStore['Todo:99']).toEqual( + expect(dataInStore["Todo:99"]).toEqual( optimisticResponse.createTodo ); } else if (count === 2) { @@ -903,7 +922,7 @@ describe('General Mutation testing', () => { }); }); - it('allows a refetchQueries prop', async () => { + it("allows a refetchQueries prop", async () => { const query = gql` query getTodo { todo { @@ -918,30 +937,30 @@ describe('General Mutation testing', () => { const queryData = { todo: { - id: '1', - text: 'todo from query', + id: "1", + text: "todo from query", completed: false, - __typename: 'Todo' + __typename: "Todo", }, - __typename: 'Query' + __typename: "Query", }; const mocksWithQuery = [ ...mocks, { request: { query }, - result: { data: queryData } + result: { data: queryData }, }, { request: { query }, - result: { data: queryData } + result: { data: queryData }, }, ]; const refetchQueries = [ { - query - } + query, + }, ]; let renderCount = 0; @@ -976,7 +995,7 @@ describe('General Mutation testing', () => { ); }); - it('allows a refetchQueries prop as string and variables have updated', async () => { + it("allows a refetchQueries prop as string and variables have updated", async () => { const query = gql` query people($first: Int) { allPeople(first: $first) { @@ -989,44 +1008,46 @@ describe('General Mutation testing', () => { const peopleData1 = { allPeople: { - people: [{ name: 'Luke Skywalker', __typename: 'Person' }], - __typename: 'People' - } + people: [{ name: "Luke Skywalker", __typename: "Person" }], + __typename: "People", + }, }; const peopleData2 = { allPeople: { - people: [{ name: 'Han Solo', __typename: 'Person' }], - __typename: 'People' - } + people: [{ name: "Han Solo", __typename: "Person" }], + __typename: "People", + }, }; const peopleData3 = { allPeople: { - people: [{ name: 'Lord Vader', __typename: 'Person' }], - __typename: 'People' - } + people: [{ name: "Lord Vader", __typename: "Person" }], + __typename: "People", + }, }; const peopleMocks = [ ...mocks, { request: { query, variables: { first: 1 } }, - result: { data: peopleData1 } + result: { data: peopleData1 }, }, { request: { query, variables: { first: 2 } }, - result: { data: peopleData2 } + result: { data: peopleData2 }, }, { request: { query, variables: { first: 2 } }, - result: { data: peopleData3 } - } + result: { data: peopleData3 }, + }, ]; - const refetchQueries = ['people']; + const refetchQueries = ["people"]; let count = 0; let testFailures: any[] = []; - const Component: React.FC>> = props => { + const Component: React.FC>> = ( + props + ) => { const [variables, setVariables] = useState(props.variables); return ( @@ -1055,7 +1076,7 @@ describe('General Mutation testing', () => { expect(resultMutation.loading).toBe(true); } else if (count === 5) { // query refetched or mutation loaded - // or both finished batched together + // or both finished batched together // hard to make assumptions here } else if (count === 6) { // both loaded @@ -1091,88 +1112,89 @@ describe('General Mutation testing', () => { }); }); - it('allows refetchQueries to be passed to the mutate function', () => new Promise((resolve, reject) => { - const query = gql` - query getTodo { - todo { - id - text - completed + 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 } - __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 - } - ]; + `; + + const queryData = { + todo: { + id: "1", + text: "todo from query", + completed: false, + __typename: "Todo", + }, + __typename: "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); + 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); } - count++; - } catch (err) { - reject(err); - } - return null; - }} - - )} - - ); + return null; + }} + + )} + + ); - render( - - - - ); + render( + + + + ); - waitFor(() => { - expect(count).toBe(4); - }).then(resolve, reject); - })); + waitFor(() => { + expect(count).toBe(4); + }).then(resolve, reject); + })); - it('has an update prop for updating the store after the mutation', async () => { + it("has an update prop for updating the store after the mutation", async () => { const update = (_proxy: DataProxy, response: ExecutionResult) => { expect(response.data).toEqual(data); }; @@ -1203,7 +1225,7 @@ describe('General Mutation testing', () => { }); }); - it('allows update to be passed to the mutate function', async () => { + it("allows update to be passed to the mutate function", async () => { const update = (_proxy: DataProxy, response: ExecutionResult) => { expect(response.data).toEqual(data); }; @@ -1230,17 +1252,17 @@ describe('General Mutation testing', () => { ); await waitFor(() => { - expect(count).toBe(3) + expect(count).toBe(3); }); }); - it('allows for overriding the options passed in the props by passing them in the mutate function', async () => { + it("allows for overriding the options passed in the props by passing them in the mutate function", async () => { const variablesProp = { - text: 'play tennis' + text: "play tennis", }; const variablesMutateFn = { - text: 'go swimming' + text: "go swimming", }; let count = 0; @@ -1263,12 +1285,12 @@ describe('General Mutation testing', () => { const mocks1 = [ { request: { query: mutation, variables: variablesProp }, - result: { data } + result: { data }, }, { request: { query: mutation, variables: variablesMutateFn }, - result: { data: data2 } - } + result: { data: data2 }, + }, ]; render( @@ -1282,40 +1304,40 @@ describe('General Mutation testing', () => { }); }); - it('updates if the client changes', async () => { + it("updates if the client changes", async () => { const link1 = mockSingleLink({ request: { query: mutation }, - result: { data } + result: { data }, }); const client1 = new ApolloClient({ link: link1, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const data3 = { createTodo: { - __typename: 'Todo', - id: '100', - text: 'After updating client.', - completed: false + __typename: "Todo", + id: "100", + text: "After updating client.", + completed: false, }, - __typename: 'Mutation' + __typename: "Mutation", }; const link2 = mockSingleLink({ request: { query: mutation }, - result: { data: data3 } + result: { data: data3 }, }); const client2 = new ApolloClient({ link: link2, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let count = 0; class Component extends React.Component { state = { - client: client1 + client: client1, }; render() { @@ -1331,7 +1353,7 @@ describe('General Mutation testing', () => { expect(result.data).toEqual(data); setTimeout(() => { this.setState({ - client: client2 + client: client2, }); }); } else if (count === 3) { @@ -1356,23 +1378,23 @@ describe('General Mutation testing', () => { }); }); - it('uses client from props instead of one provided by context', () => { + it("uses client from props instead of one provided by context", () => { const link1 = mockSingleLink({ request: { query: mutation }, - result: { data } + result: { data }, }); const client1 = new ApolloClient({ link: link1, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const link2 = mockSingleLink({ request: { query: mutation }, - result: { data: data2 } + result: { data: data2 }, }); const client2 = new ApolloClient({ link: link2, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let count = 0; @@ -1405,7 +1427,7 @@ describe('General Mutation testing', () => { }); }); - it('errors if a query is passed instead of a mutation', () => { + it("errors if a query is passed instead of a mutation", () => { const query = gql` query todos { todos { @@ -1425,14 +1447,14 @@ describe('General Mutation testing', () => { ); }).toThrowError( - 'Running a Mutation requires a graphql Mutation, but a Query was used ' + - 'instead.' + "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 () => { + it("errors when changing from mutation to a query", async () => { let didError = false; const query = gql` query todos { @@ -1444,14 +1466,14 @@ describe('General Mutation testing', () => { class Component extends React.Component { state = { - query: mutation + query: mutation, }; componentDidCatch(e: Error) { expect(e).toEqual( new Error( - 'Running a Mutation requires a graphql Mutation, but a Query ' + - 'was used instead.' + "Running a Mutation requires a graphql Mutation, but a Query " + + "was used instead." ) ); didError = true; @@ -1462,7 +1484,7 @@ describe('General Mutation testing', () => { {() => { setTimeout(() => { this.setState({ - query + query, }); }); return null; @@ -1495,7 +1517,7 @@ describe('General Mutation testing', () => { console.log = errorLogger; }); - it('errors if a subscription is passed instead of a mutation', () => { + it("errors if a subscription is passed instead of a mutation", () => { const subscription = gql` subscription todos { todos { @@ -1515,14 +1537,14 @@ describe('General Mutation testing', () => { ); }).toThrowError( - 'Running a Mutation requires a graphql Mutation, but a Subscription ' + - 'was used instead.' + "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 () => { + it("errors when changing from mutation to a subscription", async () => { let didError = false; const subscription = gql` subscription todos { @@ -1534,14 +1556,14 @@ describe('General Mutation testing', () => { class Component extends React.Component { state = { - query: mutation + query: mutation, }; componentDidCatch(e: Error) { expect(e).toEqual( new Error( - 'Running a Mutation requires a graphql Mutation, but a ' + - 'Subscription was used instead.' + "Running a Mutation requires a graphql Mutation, but a " + + "Subscription was used instead." ) ); didError = true; @@ -1553,7 +1575,7 @@ describe('General Mutation testing', () => { {() => { setTimeout(() => { this.setState({ - query: subscription + query: subscription, }); }); return null; @@ -1585,22 +1607,25 @@ describe('General Mutation testing', () => { console.log = errorLogger; }); - describe('after it has been unmounted', () => { - it('calls the onCompleted prop after the mutation is complete', async () => { + 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 context = { foo: "bar" }; const onCompletedFn = jest.fn(); const checker = () => { setTimeout(() => { success = true; - expect(onCompletedFn).toHaveBeenCalledWith(data, expect.objectContaining({ context })); + expect(onCompletedFn).toHaveBeenCalledWith( + data, + expect.objectContaining({ context }) + ); }, 100); }; class Component extends React.Component { state = { - called: false + called: false, }; render() { @@ -1615,7 +1640,7 @@ describe('General Mutation testing', () => { createTodo({ context }).finally(() => { finished = true; }); - expect(onCompletedFn).toHaveBeenCalledWith + expect(onCompletedFn).toHaveBeenCalledWith; // eslint-disable-next-line testing-library/await-async-utils this.setState({ called: true }, checker); }); @@ -1633,32 +1658,38 @@ describe('General Mutation testing', () => { ); - 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 }); + 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 () => { + 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'); + expect(error.message).toEqual("error occurred"); onErrorCalled = true; } @@ -1680,8 +1711,8 @@ describe('General Mutation testing', () => { const mockError = [ { request: { query: mutation }, - error: new Error('error occurred') - } + error: new Error("error occurred"), + }, ]; render( @@ -1690,23 +1721,29 @@ describe('General Mutation testing', () => { ); - 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 }); + 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 index ea18591712f..5e788dd11aa 100644 --- a/src/react/components/__tests__/client/Query.test.tsx +++ b/src/react/components/__tests__/client/Query.test.tsx @@ -1,15 +1,15 @@ -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 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"; const allPeopleQuery: DocumentNode = gql` query people { @@ -28,7 +28,7 @@ interface Data { } const allPeopleData: Data = { - allPeople: { people: [{ name: 'Luke Skywalker' }] }, + allPeople: { people: [{ name: "Luke Skywalker" }] }, }; const allPeopleMocks = [ { @@ -39,8 +39,8 @@ const allPeopleMocks = [ const AllPeopleQuery = Query; -describe('Query component', () => { - itAsync('calls the children prop', (resolve, reject) => { +describe("Query component", () => { + itAsync("calls the children prop", (resolve, reject) => { let finished = false; const link = mockSingleLink({ request: { query: allPeopleQuery }, @@ -111,11 +111,11 @@ describe('Query component', () => { ); waitFor(() => { - expect(finished).toBe(true) + expect(finished).toBe(true); }).then(resolve, reject); }); - it('renders using the children prop', async () => { + it("renders using the children prop", async () => { const Component = () => ( {(_: any) =>
test
}
); @@ -127,11 +127,11 @@ describe('Query component', () => { ); await waitFor(() => { - expect(screen.getByText('test')).toBeTruthy(); + expect(screen.getByText("test")).toBeTruthy(); }); }); - describe('result provides', () => { + describe("result provides", () => { let consoleWarn = console.warn; beforeAll(() => { console.warn = () => null; @@ -141,7 +141,7 @@ describe('Query component', () => { console.warn = consoleWarn; }); - itAsync('client', (resolve, reject) => { + itAsync("client", (resolve, reject) => { let count = 0; const queryWithVariables: DocumentNode = gql` query people($first: Int) { @@ -195,12 +195,12 @@ describe('Query component', () => { }).then(resolve, reject); }); - itAsync('error', (resolve, reject) => { + itAsync("error", (resolve, reject) => { let finished = false; const mockError = [ { request: { query: allPeopleQuery }, - error: new Error('error occurred'), + error: new Error("error occurred"), }, ]; @@ -211,9 +211,7 @@ describe('Query component', () => { return null; } try { - expect(result.error).toEqual( - new Error('error occurred') - ); + expect(result.error).toEqual(new Error("error occurred")); finished = true; } catch (error) { reject(error); @@ -234,7 +232,7 @@ describe('Query component', () => { }).then(resolve, reject); }); - itAsync('refetch', (resolve, reject) => { + itAsync("refetch", (resolve, reject) => { const queryRefetch: DocumentNode = gql` query people($first: Int) { allPeople(first: $first) { @@ -245,9 +243,9 @@ describe('Query component', () => { } `; - const data1 = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; - const data2 = { allPeople: { people: [{ name: 'Han Solo' }] } }; - const data3 = { allPeople: { people: [{ name: 'Darth Vader' }] } }; + 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, @@ -336,9 +334,9 @@ describe('Query component', () => { }).then(resolve, reject); }); - itAsync('fetchMore', (resolve, reject) => { - const data1 = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; - const data2 = { allPeople: { people: [{ name: 'Han Solo' }] } }; + itAsync("fetchMore", (resolve, reject) => { + const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; + const data2 = { allPeople: { people: [{ name: "Han Solo" }] } }; const variables = { first: 2, @@ -415,10 +413,10 @@ describe('Query component', () => { 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' }] } }; + 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 = [ { @@ -480,10 +478,10 @@ describe('Query component', () => { 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' }] } }; + 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 = [ { @@ -531,9 +529,9 @@ describe('Query component', () => { 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' }] } }; + itAsync("updateQuery", (resolve, reject) => { + const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; + const data2 = { allPeople: { people: [{ name: "Han Solo" }] } }; const variables = { first: 2, }; @@ -594,11 +592,11 @@ describe('Query component', () => { }); }); - describe('props allow', () => { - it('custom fetch-policy', async () => { + describe("props allow", () => { + it("custom fetch-policy", async () => { let count = 0; const Component = () => ( - + {(result: any) => { if (!result.loading) { expect(result.networkStatus).toBe(NetworkStatus.ready); @@ -620,7 +618,7 @@ describe('Query component', () => { }); }); - it('default fetch-policy', async () => { + it("default fetch-policy", async () => { let count = 0; const Component = () => ( @@ -636,7 +634,7 @@ describe('Query component', () => { render( @@ -648,9 +646,9 @@ describe('Query component', () => { }); }); - itAsync('notifyOnNetworkStatusChange', (resolve, reject) => { - const data1 = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; - const data2 = { allPeople: { people: [{ name: 'Han Solo' }] } }; + itAsync("notifyOnNetworkStatusChange", (resolve, reject) => { + const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; + const data2 = { allPeople: { people: [{ name: "Han Solo" }] } }; const mocks = [ { @@ -700,14 +698,14 @@ describe('Query component', () => { ); waitFor(() => { - expect(count).toBe(4) + 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' }] } }; + 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 = [ { @@ -757,7 +755,7 @@ describe('Query component', () => { waitFor(() => expect(count).toBe(POLL_COUNT)).then(resolve, reject); }); - itAsync('skip', (resolve, reject) => { + itAsync("skip", (resolve, reject) => { let finished = false; const Component = () => ( @@ -786,7 +784,7 @@ describe('Query component', () => { }).then(resolve, reject); }); - it('onCompleted with data', async () => { + it("onCompleted with data", async () => { const query = gql` query people($first: Int) { allPeople(first: $first) { @@ -797,8 +795,8 @@ describe('Query component', () => { } `; - const data1 = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; - const data2 = { allPeople: { people: [{ name: 'Han Solo' }] } }; + const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; + const data2 = { allPeople: { people: [{ name: "Han Solo" }] } }; const mocks = [ { request: { query, variables: { first: 1 } }, @@ -860,9 +858,9 @@ describe('Query component', () => { }); }); - itAsync('onError with data', (resolve, reject) => { + itAsync("onError with data", (resolve, reject) => { let finished = false; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; const mocks = [ { @@ -901,8 +899,8 @@ describe('Query component', () => { }); }); - describe('props disallow', () => { - it('Mutation provided as query', () => { + describe("props disallow", () => { + it("Mutation provided as query", () => { const mutation = gql` mutation submitRepository { submitRepository(repoFullName: "apollographql/apollo-client") { @@ -921,14 +919,14 @@ describe('Query component', () => { ); }).toThrowError( - 'Running a Query requires a graphql Query, but a Mutation was used ' + - 'instead.' + "Running a Query requires a graphql Query, but a Mutation was used " + + "instead." ); console.error = errorLogger; }); - it('Subscription provided as query', () => { + it("Subscription provided as query", () => { const subscription = gql` subscription onCommentAdded($repoFullName: String!) { commentAdded(repoFullName: $repoFullName) { @@ -948,19 +946,19 @@ describe('Query component', () => { ); }).toThrowError( - 'Running a Query requires a graphql Query, but a Subscription was ' + - 'used instead.' + "Running a Query requires a graphql Query, but a Subscription was " + + "used instead." ); console.error = errorLogger; }); - itAsync('onCompleted with error', (resolve, reject) => { + itAsync("onCompleted with error", (resolve, reject) => { let finished = false; const mockError = [ { request: { query: allPeopleQuery }, - error: new Error('error occurred'), + error: new Error("error occurred"), }, ]; @@ -989,9 +987,9 @@ describe('Query component', () => { }).then(resolve, reject); }); - it('onError with error', async () => { + it("onError with error", async () => { let finished = false; - const error = new Error('error occurred'); + const error = new Error("error occurred"); const mockError = [ { request: { query: allPeopleQuery }, @@ -1024,8 +1022,8 @@ describe('Query component', () => { }); }); - describe('should update', () => { - itAsync('if props change', (resolve, reject) => { + describe("should update", () => { + itAsync("if props change", (resolve, reject) => { const query = gql` query people($first: Int) { allPeople(first: $first) { @@ -1036,8 +1034,8 @@ describe('Query component', () => { } `; - const data1 = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; - const data2 = { allPeople: { people: [{ name: 'Han Solo' }] } }; + const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; + const data2 = { allPeople: { people: [{ name: "Han Solo" }] } }; const mocks = [ { request: { query, variables: { first: 1 } }, @@ -1116,7 +1114,7 @@ describe('Query component', () => { waitFor(() => expect(count).toBe(4)).then(resolve, reject); }); - itAsync('if the query changes', (resolve, reject) => { + itAsync("if the query changes", (resolve, reject) => { const query1 = allPeopleQuery; const query2 = gql` query people { @@ -1129,8 +1127,8 @@ describe('Query component', () => { } `; - const data1 = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; - const data2 = { allPeople: { people: [{ name: 'Han Solo', id: '1' }] } }; + const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; + const data2 = { allPeople: { people: [{ name: "Han Solo", id: "1" }] } }; const mocks = [ { request: { query: query1 }, @@ -1193,7 +1191,7 @@ describe('Query component', () => { waitFor(() => expect(count).toBe(2)).then(resolve, reject); }); - it('with data while loading', async () => { + it("with data while loading", async () => { const query = gql` query people($first: Int) { allPeople(first: $first) { @@ -1206,11 +1204,11 @@ describe('Query component', () => { const data1 = { allPeople: { - people: [{ name: 'Luke Skywalker' }], + people: [{ name: "Luke Skywalker" }], }, }; const data2 = { - allPeople: { people: [{ name: 'Han Solo' }] }, + allPeople: { people: [{ name: "Han Solo" }] }, }; const mocks = [ { @@ -1256,7 +1254,9 @@ describe('Query component', () => { case 3: expect(result.loading).toBe(true); expect(result.data).toBeUndefined(); - expect(result.networkStatus).toBe(NetworkStatus.setVariables); + expect(result.networkStatus).toBe( + NetworkStatus.setVariables + ); break; case 4: expect(result.loading).toBe(false); @@ -1281,125 +1281,128 @@ describe('Query component', () => { ); }); - itAsync('should update if a manual `refetch` is triggered after a state change', (resolve, reject) => { - const query: DocumentNode = gql` - query { - allPeople { - people { - name + 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 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 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 }), - }); + const client = new ApolloClient({ + link, + cache: new InMemoryCache({ addTypename: false }), + }); - let count = 0; + let count = 0; - class SomeComponent extends React.Component { - constructor(props: any) { - super(props); - this.state = { - open: false, - }; - this.toggle = this.toggle.bind(this); - } + 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, - })); - } + 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'); + 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); } - count += 1; - } catch (error) { - reject(error); - } - return null; - }} - - ); + return null; + }} + + ); + } } - } - render(); + render(); - waitFor(() => { - expect(count).toBe(7) - }).then(resolve, reject); - }); + waitFor(() => { + expect(count).toBe(7); + }).then(resolve, reject); + } + ); }); - it('should error if the query changes type to a subscription', async () => { + it("should error if the query changes type to a subscription", async () => { let finished = false; const subscription = gql` subscription onCommentAdded($repoFullName: String!) { @@ -1419,8 +1422,8 @@ describe('Query component', () => { componentDidCatch(error: any) { const expectedError = new Error( - 'Running a Query requires a graphql Query, but a Subscription was ' + - 'used instead.' + "Running a Query requires a graphql Query, but a Subscription was " + + "used instead." ); expect(error).toEqual(expectedError); finished = true; @@ -1446,13 +1449,16 @@ describe('Query component', () => { ); - await waitFor(() => { - expect(finished).toBe(true); - }, { interval: 1 }); + await waitFor( + () => { + expect(finished).toBe(true); + }, + { interval: 1 } + ); console.error = errorLog; }); - it('should be able to refetch after there was a network error', async () => { + it("should be able to refetch after there was a network error", async () => { const query: DocumentNode = gql` query somethingelse { allPeople(first: 1) { @@ -1463,12 +1469,12 @@ describe('Query component', () => { } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; - const dataTwo = { allPeople: { people: [{ name: 'Princess Leia' }] } }; + 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 }, + { request: { query }, error: new Error("This is an error!") }, + { request: { query }, result: { data: dataTwo }, delay: 10 } ); const client = new ApolloClient({ link, @@ -1494,12 +1500,10 @@ describe('Query component', () => { case 1: // First result is loaded, run a refetch to get the second result // which is an error. - expect(result.data.allPeople).toEqual( - data.allPeople - ); + expect(result.data.allPeople).toEqual(data.allPeople); setTimeout(() => { result.refetch().then(() => { - fail('Expected error value on first refetch.'); + fail("Expected error value on first refetch."); }, noop); }, 0); break; @@ -1510,7 +1514,7 @@ describe('Query component', () => { case 3: setTimeout(() => { result.refetch().catch(() => { - fail('Expected good data on second refetch.'); + fail("Expected good data on second refetch."); }); }, 0); // fallthrough @@ -1529,7 +1533,7 @@ describe('Query component', () => { expect(result.data.allPeople).toEqual(dataTwo.allPeople); break; default: - throw new Error('Unexpected fall through'); + throw new Error("Unexpected fall through"); } } catch (e) { // if we throw the error inside the component, @@ -1558,7 +1562,7 @@ describe('Query component', () => { }); itAsync( - 'should not persist previous result errors when a subsequent valid result is received', + "should not persist previous result errors when a subsequent valid result is received", (resolve, reject) => { const query: DocumentNode = gql` query somethingelse($variable: Boolean) { @@ -1570,7 +1574,7 @@ describe('Query component', () => { } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; const variableGood = { variable: true }; const variableBad = { variable: false }; @@ -1590,7 +1594,7 @@ describe('Query component', () => { variables: variableBad, }, result: { - errors: [new Error('This is an error!')], + errors: [new Error("This is an error!")], }, }, { @@ -1622,11 +1626,7 @@ describe('Query component', () => { // Change query variables to trigger bad result. setTimeout(() => { render( - + {(result: any) => { return ; }} @@ -1643,11 +1643,7 @@ describe('Query component', () => { // Change query variables to trigger a good result. setTimeout(() => { render( - + {(result: any) => { return ; }} @@ -1661,7 +1657,7 @@ describe('Query component', () => { expect(props.data.allPeople).toBeTruthy(); break; default: - reject('Unknown count'); + reject("Unknown count"); } } catch (error) { reject(error); @@ -1681,7 +1677,7 @@ describe('Query component', () => { } ); - it('should support mixing setState and onCompleted', async () => { + it("should support mixing setState and onCompleted", async () => { const query = gql` query people($first: Int) { allPeople(first: $first) { @@ -1692,8 +1688,8 @@ describe('Query component', () => { } `; - const data1 = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; - const data2 = { allPeople: { people: [{ name: 'Han Solo' }] } }; + const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; + const data2 = { allPeople: { people: [{ name: "Han Solo" }] } }; const mocks = [ { request: { query, variables: { first: 1 } }, @@ -1781,61 +1777,63 @@ describe('Query component', () => { }); }); - 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, + 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"), }, - }; - onError = () => { - onErrorCallCount += 1; - this.setState({ causeUpdate: true }); - }; - render() { - return ( - - {({ loading }: any) => { - if (!loading) { - setTimeout(unmount); - } - return null; - }} - - ); + ]; + + 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; + unmount = render( + + + + ).unmount; - waitFor(() => { - expect(onErrorCallCount).toBe(1); - }).then(resolve, reject); - }); + waitFor(() => { + expect(onErrorCallCount).toBe(1); + }).then(resolve, reject); + } + ); - describe('Partial refetching', () => { + describe("Partial refetching", () => { let errorSpy!: ReturnType; beforeEach(() => { - errorSpy = jest.spyOn(console, 'error') - .mockImplementation(() => {}); + errorSpy = jest.spyOn(console, "error").mockImplementation(() => {}); }); afterAll(() => { @@ -1846,10 +1844,10 @@ describe('Query component', () => { // 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`', + "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 { @@ -1863,7 +1861,7 @@ describe('Query component', () => { let count = 0; const allPeopleData = { - allPeople: { people: [{ name: 'Luke Skywalker' }] }, + allPeople: { people: [{ name: "Luke Skywalker" }] }, }; const query = allPeopleQuery; const link = mockSingleLink( @@ -1885,7 +1883,7 @@ describe('Query component', () => { if (!loading) { expect(data).toEqual(allPeopleData); expect(errorSpy).toHaveBeenCalledTimes(1); - expect(errorSpy.mock.calls[0][0]).toMatch('Missing field'); + expect(errorSpy.mock.calls[0][0]).toMatch("Missing field"); } } catch (err) { reject(err); @@ -1908,8 +1906,8 @@ describe('Query component', () => { ); itAsync.skip( - 'should not refetch when an empty partial is returned if the ' + - '`partialRefetch` prop is false/not set', + "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; @@ -1940,16 +1938,16 @@ describe('Query component', () => { ); - waitFor(() => { - expect(finished).toBe(true); - }).then(resolve, reject); + 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', + "should keep data for a `Query` component using `no-cache` when the " + + "tree is re-rendered", (resolve, reject) => { const query1 = allPeopleQuery; @@ -1971,14 +1969,14 @@ describe('Query component', () => { const allThingsData: ThingData = { allThings: { - thing: [{ description: 'Thing 1' }, { description: 'Thing 2' }], + 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 } }, + { request: { query: query1 }, result: { data: allPeopleData } } ); const client = new ApolloClient({ @@ -2029,7 +2027,7 @@ describe('Query component', () => { } ); - describe('Return partial data', () => { + describe("Return partial data", () => { const origConsoleWarn = console.warn; beforeAll(() => { @@ -2040,7 +2038,7 @@ describe('Query component', () => { console.warn = origConsoleWarn; }); - it('should not return partial cache data when `returnPartialData` is false', async () => { + it("should not return partial cache data when `returnPartialData` is false", async () => { let finished = false; const cache = new InMemoryCache(); const client = new ApolloClient({ @@ -2066,15 +2064,15 @@ describe('Query component', () => { data: { cars: [ { - __typename: 'Car', - make: 'Ford', - model: 'Mustang', - vin: 'PONY123', + __typename: "Car", + make: "Ford", + model: "Mustang", + vin: "PONY123", repairs: [ { - __typename: 'Repair', - date: '2019-05-08', - description: 'Could not get after it.', + __typename: "Repair", + date: "2019-05-08", + description: "Could not get after it.", }, ], }, @@ -2108,11 +2106,11 @@ describe('Query component', () => { render(); await waitFor(() => { - expect(finished).toBe(true) - }) + expect(finished).toBe(true); + }); }); - it('should return partial cache data when `returnPartialData` is true', async () => { + it("should return partial cache data when `returnPartialData` is true", async () => { let finished = false; const cache = new InMemoryCache(); const client = new ApolloClient({ @@ -2138,15 +2136,15 @@ describe('Query component', () => { data: { cars: [ { - __typename: 'Car', - make: 'Ford', - model: 'Mustang', - vin: 'PONY123', + __typename: "Car", + make: "Ford", + model: "Mustang", + vin: "PONY123", repairs: [ { - __typename: 'Repair', - date: '2019-05-08', - description: 'Could not get after it.', + __typename: "Repair", + date: "2019-05-08", + description: "Could not get after it.", }, ], }, @@ -2172,11 +2170,11 @@ describe('Query component', () => { expect(data).toEqual({ cars: [ { - __typename: 'Car', + __typename: "Car", repairs: [ { - __typename: 'Repair', - date: '2019-05-08', + __typename: "Repair", + date: "2019-05-08", }, ], }, diff --git a/src/react/components/__tests__/client/Subscription.test.tsx b/src/react/components/__tests__/client/Subscription.test.tsx index 7fd0ccd95e0..f11e262c2db 100644 --- a/src/react/components/__tests__/client/Subscription.test.tsx +++ b/src/react/components/__tests__/client/Subscription.test.tsx @@ -1,21 +1,21 @@ -import React from 'react'; -import gql from 'graphql-tag'; -import { render, waitFor } from '@testing-library/react'; +import React from "react"; +import gql from "graphql-tag"; +import { render, waitFor } from "@testing-library/react"; -import { ApolloClient } from '../../../../core'; -import { InMemoryCache as Cache } from '../../../../cache'; -import { ApolloProvider } from '../../../context'; -import { ApolloLink, Operation } from '../../../../link/core'; -import { itAsync, MockSubscriptionLink } from '../../../../testing'; -import { Subscription } from '../../Subscription'; +import { ApolloClient } from "../../../../core"; +import { InMemoryCache as Cache } from "../../../../cache"; +import { ApolloProvider } from "../../../context"; +import { ApolloLink, Operation } from "../../../../link/core"; +import { itAsync, MockSubscriptionLink } from "../../../../testing"; +import { Subscription } from "../../Subscription"; const results = [ - 'Luke Skywalker', - 'Han Solo', - 'Darth Vader', - 'Leia Skywalker' -].map(name => ({ - result: { data: { user: { name } } } + "Luke Skywalker", + "Han Solo", + "Darth Vader", + "Leia Skywalker", +].map((name) => ({ + result: { data: { user: { name } } }, })); beforeEach(() => { @@ -34,10 +34,10 @@ const cache = new Cache({ addTypename: false }); const link = new MockSubscriptionLink(); const client = new ApolloClient({ link, - cache + cache, }); -itAsync('executes the subscription', (resolve, reject) => { +itAsync("executes the subscription", (resolve, reject) => { let renderCount = 0; const Component = () => ( @@ -88,7 +88,7 @@ itAsync('executes the subscription', (resolve, reject) => { waitFor(() => expect(renderCount).toBe(5)).then(resolve, reject); }); -it('calls onData if given', async () => { +it("calls onData if given", async () => { let count = 0; const Component = () => ( @@ -117,8 +117,10 @@ it('calls onData if given', async () => { await waitFor(() => expect(count).toBe(4)); }); -it('calls onSubscriptionData with deprecation warning if given', async () => { - const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); +it("calls onSubscriptionData with deprecation warning if given", async () => { + const consoleWarnSpy = jest + .spyOn(console, "warn") + .mockImplementation(() => {}); let count = 0; const Component = () => ( @@ -149,12 +151,12 @@ it('calls onSubscriptionData with deprecation warning if given', async () => { if (count >= 3) clearInterval(interval); }, 10); - await waitFor(() => expect(count).toBe(4)) + await waitFor(() => expect(count).toBe(4)); consoleWarnSpy.mockRestore(); }); -it('should call onComplete if specified', async () => { +it("should call onComplete if specified", async () => { let count = 0; let done = false; @@ -184,8 +186,10 @@ it('should call onComplete if specified', async () => { await waitFor(() => expect(done).toBeTruthy()); }); -it('should call onSubscriptionComplete with deprecation warning if specified', async () => { - const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); +it("should call onSubscriptionComplete with deprecation warning if specified", async () => { + const consoleWarnSpy = jest + .spyOn(console, "warn") + .mockImplementation(() => {}); let count = 0; let done = false; @@ -222,73 +226,76 @@ it('should call onSubscriptionComplete with deprecation warning if specified', a consoleWarnSpy.mockRestore(); }); -itAsync('executes subscription for the variables passed in the props', (resolve, reject) => { - const subscriptionWithVariables = gql` - subscription UserInfo($name: String) { - user(name: $name) { - name +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' }; + const variables = { name: "Luke Skywalker" }; - class MockSubscriptionLinkOverride extends MockSubscriptionLink { - request(req: Operation) { - try { - expect(req.variables).toEqual(variables); - } catch (error) { - reject(error); + class MockSubscriptionLinkOverride extends MockSubscriptionLink { + request(req: Operation) { + try { + expect(req.variables).toEqual(variables); + } catch (error) { + reject(error); + } + return super.request(req); } - return super.request(req); } - } - const mockLink = new MockSubscriptionLinkOverride(); - - const mockClient = new ApolloClient({ - link: mockLink, - cache - }); + const mockLink = new MockSubscriptionLinkOverride(); - let count = 0; + const mockClient = new ApolloClient({ + link: mockLink, + cache, + }); - const Component = () => ( - - {(result: any) => { - const { loading, data } = result; + let count = 0; - try { - if (count === 0) { - expect(loading).toBe(true); - } else if (count === 1) { - expect(loading).toBe(false); - expect(data).toEqual(results[0].result.data); + 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); } - } catch (error) { - reject(error); - } - count++; - return null; - }} - - ); + count++; + return null; + }} + + ); - render( - - - - ); + render( + + + + ); - mockLink.simulateResult(results[0]); + mockLink.simulateResult(results[0]); - waitFor(() => expect(count).toBe(2)).then(resolve, reject); -}); + waitFor(() => expect(count).toBe(2)).then(resolve, reject); + } +); -itAsync('does not execute if variables have not changed', (resolve, reject) => { +itAsync("does not execute if variables have not changed", (resolve, reject) => { const subscriptionWithVariables = gql` subscription UserInfo($name: String) { user(name: $name) { @@ -297,7 +304,7 @@ itAsync('does not execute if variables have not changed', (resolve, reject) => { } `; - const name = 'Luke Skywalker'; + const name = "Luke Skywalker"; class MockSubscriptionLinkOverride extends MockSubscriptionLink { request(req: Operation) { @@ -314,7 +321,7 @@ itAsync('does not execute if variables have not changed', (resolve, reject) => { const mockClient = new ApolloClient({ link: mockLink, - cache + cache, }); let count = 0; @@ -359,7 +366,7 @@ itAsync('does not execute if variables have not changed', (resolve, reject) => { waitFor(() => expect(count).toBe(3)).then(resolve, reject); }); -itAsync('renders an error', (resolve, reject) => { +itAsync("renders an error", (resolve, reject) => { const subscriptionWithVariables = gql` subscription UserInfo($name: String) { user(name: $name) { @@ -369,11 +376,11 @@ itAsync('renders an error', (resolve, reject) => { `; const variables = { - name: 'Luke Skywalker' + name: "Luke Skywalker", }; const subscriptionError = { - error: new Error('error occurred') + error: new Error("error occurred"), }; let count = 0; @@ -390,7 +397,7 @@ itAsync('renders an error', (resolve, reject) => { expect(error).toBeUndefined(); } else if (count === 1) { expect(loading).toBe(false); - expect(error).toEqual(new Error('error occurred')); + expect(error).toEqual(new Error("error occurred")); expect(data).toBeUndefined(); } } catch (error) { @@ -414,12 +421,12 @@ itAsync('renders an error', (resolve, reject) => { waitFor(() => expect(count).toBe(2)).then(resolve, reject); }); -describe('should update', () => { - it('if the client changes', async () => { +describe("should update", () => { + it("if the client changes", async () => { const link2 = new MockSubscriptionLink(); const client2 = new ApolloClient({ link: link2, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let count = 0; @@ -427,7 +434,7 @@ describe('should update', () => { class Component extends React.Component { state = { - client: client + client: client, }; render() { @@ -446,14 +453,14 @@ describe('should update', () => { setTimeout(() => { this.setState( { - client: client2 + client: client2, }, () => { link2.simulateResult(results[1]); } ); }); - // fallthrough + // fallthrough case 2: expect(loading).toBeFalsy(); expect(data).toEqual(results[0].result.data); @@ -492,7 +499,7 @@ describe('should update', () => { }); }); - itAsync('if the query changes', (resolve, reject) => { + itAsync("if the query changes", (resolve, reject) => { const subscriptionHero = gql` subscription HeroInfo { hero { @@ -505,30 +512,30 @@ describe('should update', () => { result: { data: { hero: { - name: 'Chewie' - } - } - } + name: "Chewie", + }, + }, + }, }; const userLink = new MockSubscriptionLink(); const heroLink = new MockSubscriptionLink(); const linkCombined = new ApolloLink((o, f) => (f ? f(o) : null)).split( - ({ operationName }) => operationName === 'HeroInfo', + ({ operationName }) => operationName === "HeroInfo", heroLink, userLink ); const mockClient = new ApolloClient({ link: linkCombined, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let count = 0; class Component extends React.Component { state = { - subscription + subscription, }; render() { @@ -546,14 +553,14 @@ describe('should update', () => { setTimeout(() => { this.setState( { - subscription: subscriptionHero + subscription: subscriptionHero, }, () => { heroLink.simulateResult(heroResult); } ); }); - // fallthrough + // fallthrough case 2: expect(loading).toBeFalsy(); expect(data).toEqual(results[0].result.data); @@ -589,7 +596,7 @@ describe('should update', () => { waitFor(() => expect(count).toBe(5)).then(resolve, reject); }); - itAsync('if the variables change', (resolve, reject) => { + itAsync("if the variables change", (resolve, reject) => { const subscriptionWithVariables = gql` subscription UserInfo($name: String) { user(name: $name) { @@ -598,33 +605,33 @@ describe('should update', () => { } `; - const variablesLuke = { name: 'Luke Skywalker' }; - const variablesHan = { name: 'Han Solo' }; + const variablesLuke = { name: "Luke Skywalker" }; + const variablesHan = { name: "Han Solo" }; const dataLuke = { user: { - name: 'Luke Skywalker' - } + name: "Luke Skywalker", + }, }; const dataHan = { user: { - name: 'Han Solo' - } + name: "Han Solo", + }, }; const mockLink = new MockSubscriptionLink(); const mockClient = new ApolloClient({ link: mockLink, - cache + cache, }); let count = 0; class Component extends React.Component { state = { - variables: variablesLuke + variables: variablesLuke, }; render() { @@ -645,14 +652,16 @@ describe('should update', () => { setTimeout(() => { this.setState( { - variables: variablesHan + variables: variablesHan, }, () => { - mockLink.simulateResult({ result: { data: dataHan } }); + mockLink.simulateResult({ + result: { data: dataHan }, + }); } ); }); - // fallthrough + // fallthrough case 2: expect(loading).toBeFalsy(); expect(data).toEqual(dataLuke); @@ -690,20 +699,20 @@ describe('should update', () => { }); }); -describe('should not update', () => { - const variablesLuke = { name: 'Luke Skywalker' }; - const variablesHan = { name: 'Han Solo' }; +describe("should not update", () => { + const variablesLuke = { name: "Luke Skywalker" }; + const variablesHan = { name: "Han Solo" }; const dataLuke = { user: { - name: 'Luke Skywalker' - } + name: "Luke Skywalker", + }, }; const dataHan = { user: { - name: 'Han Solo' - } + name: "Han Solo", + }, }; class MockSubscriptionLinkOverride extends MockSubscriptionLink { @@ -714,23 +723,23 @@ describe('should not update', () => { } simulateResult() { - if (this.variables.name === 'Luke Skywalker') { + if (this.variables.name === "Luke Skywalker") { return super.simulateResult({ result: { - data: dataLuke - } + data: dataLuke, + }, }); - } else if (this.variables.name === 'Han Solo') { + } else if (this.variables.name === "Han Solo") { return super.simulateResult({ result: { - data: dataHan - } + data: dataHan, + }, }); } } } - itAsync('if shouldResubscribe is false', (resolve, reject) => { + itAsync("if shouldResubscribe is false", (resolve, reject) => { const subscriptionWithVariables = gql` subscription UserInfo($name: String) { user(name: $name) { @@ -743,14 +752,14 @@ describe('should not update', () => { const mockClient = new ApolloClient({ link: mockLink, - cache + cache, }); let count = 0; class Component extends React.Component { state = { - variables: variablesLuke + variables: variablesLuke, }; render() { @@ -772,7 +781,7 @@ describe('should not update', () => { setTimeout(() => { this.setState( { - variables: variablesHan + variables: variablesHan, }, () => { mockLink.simulateResult(); @@ -806,7 +815,7 @@ describe('should not update', () => { waitFor(() => expect(count).toBe(4)).then(resolve, reject); }); - itAsync('if shouldResubscribe returns false', (resolve, reject) => { + itAsync("if shouldResubscribe returns false", (resolve, reject) => { const subscriptionWithVariables = gql` subscription UserInfo($name: String) { user(name: $name) { @@ -819,14 +828,14 @@ describe('should not update', () => { const mockClient = new ApolloClient({ link: mockLink, - cache + cache, }); let count = 0; class Component extends React.Component { state = { - variables: variablesLuke + variables: variablesLuke, }; render() { @@ -848,7 +857,7 @@ describe('should not update', () => { setTimeout(() => { this.setState( { - variables: variablesHan + variables: variablesHan, }, () => { mockLink.simulateResult(); diff --git a/src/react/components/__tests__/ssr/getDataFromTree.test.tsx b/src/react/components/__tests__/ssr/getDataFromTree.test.tsx index 198cdcd7cd9..6441008606e 100644 --- a/src/react/components/__tests__/ssr/getDataFromTree.test.tsx +++ b/src/react/components/__tests__/ssr/getDataFromTree.test.tsx @@ -1,22 +1,28 @@ /** @jest-environment node */ -import React from 'react'; -import gql from 'graphql-tag'; -import { DocumentNode } from 'graphql'; +import React from "react"; +import gql from "graphql-tag"; +import { DocumentNode } from "graphql"; -import { ApolloClient } 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'; +import { ApolloClient } 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; +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 + declare context: React.ContextType; render() { return
{this.context.text}
; @@ -24,13 +30,13 @@ describe('SSR', () => { } return getDataFromTree(, { - text: 'oyez' - }).then(html => { - expect(html).toEqual('
oyez
'); + text: "oyez", + }).then((html) => { + expect(html).toEqual("
oyez
"); }); }); - it('should run through all of the queries (also defined via Query component) that want SSR', () => { + it("should run through all of the queries (also defined via Query component) that want SSR", () => { const query = gql` { currentUser { @@ -38,15 +44,15 @@ describe('SSR', () => { } } `; - const data1 = { currentUser: { firstName: 'James' } }; + const data1 = { currentUser: { firstName: "James" } }; const link = mockSingleLink({ request: { query }, result: { data: data1 }, - delay: 50 + delay: 50, }); const apolloClient = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Data { @@ -59,7 +65,7 @@ describe('SSR', () => { {({ data, loading }: { data: Data; loading: boolean }) => (
- {loading || !data ? 'loading' : data.currentUser!.firstName} + {loading || !data ? "loading" : data.currentUser!.firstName}
)}
@@ -71,57 +77,60 @@ describe('SSR', () => { ); - return getDataFromTree(app).then(markup => { + 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 + 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 - } + `; + const link = mockSingleLink({ + request: { query }, + result: { + data: { + allPeople: { + people: null, + }, + }, + errors: [new Error("this is an error")], }, - errors: [new Error('this is an error')] - } - }); + }); - const client = new ApolloClient({ - link, - cache: new Cache({ addTypename: false }) - }); + 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; - }} - - - ); + 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); - }); + getDataFromTree(app); + } + ); }); }); diff --git a/src/react/components/__tests__/ssr/server.test.tsx b/src/react/components/__tests__/ssr/server.test.tsx index ef4d3756d62..7d50399515d 100644 --- a/src/react/components/__tests__/ssr/server.test.tsx +++ b/src/react/components/__tests__/ssr/server.test.tsx @@ -1,5 +1,5 @@ /** @jest-environment node */ -import React from 'react'; +import React from "react"; import { print, graphql as execute, @@ -7,111 +7,111 @@ import { GraphQLObjectType, GraphQLList, GraphQLString, - GraphQLID -} from 'graphql'; -import gql from 'graphql-tag'; + 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'; +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 planetMap = new Map([["Planet:1", { id: "Planet:1", name: "Tatooine" }]]); const shipMap = new Map([ [ - 'Ship:2', + "Ship:2", { - id: 'Ship:2', - name: 'CR90 corvette', - films: ['Film:4', 'Film:6', 'Film:3'] - } + id: "Ship:2", + name: "CR90 corvette", + films: ["Film:4", "Film:6", "Film:3"], + }, ], [ - 'Ship:3', + "Ship:3", { - id: 'Ship:3', - name: 'Star Destroyer', - films: ['Film:4', 'Film:5', 'Film:6'] - } - ] + 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' }] + ["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', + name: "Planet", fields: { id: { type: GraphQLID }, - name: { type: GraphQLString } - } + name: { type: GraphQLString }, + }, }); const FilmType = new GraphQLObjectType({ - name: 'Film', + name: "Film", fields: { id: { type: GraphQLID }, - title: { type: GraphQLString } - } + title: { type: GraphQLString }, + }, }); const ShipType = new GraphQLObjectType({ - name: 'Ship', + name: "Ship", fields: { id: { type: GraphQLID }, name: { type: GraphQLString }, films: { type: new GraphQLList(FilmType), - resolve: ({ films }) => films.map((id: string) => filmMap.get(id)) - } - } + resolve: ({ films }) => films.map((id: string) => filmMap.get(id)), + }, + }, }); const QueryType = new GraphQLObjectType({ - name: 'Query', + name: "Query", fields: { allPlanets: { type: new GraphQLList(PlanetType), - resolve: () => Array.from(planetMap.values()) + resolve: () => Array.from(planetMap.values()), }, allShips: { type: new GraphQLList(ShipType), - resolve: () => Array.from(shipMap.values()) + resolve: () => Array.from(shipMap.values()), }, ship: { type: ShipType, args: { id: { type: GraphQLID } }, - resolve: (_, { id }) => shipMap.get(id) + resolve: (_, { id }) => shipMap.get(id), }, film: { type: FilmType, args: { id: { type: GraphQLID } }, - resolve: (_, { id }) => filmMap.get(id) - } - } + resolve: (_, { id }) => filmMap.get(id), + }, + }, }); const Schema = new GraphQLSchema({ query: QueryType }); -describe('SSR', () => { - it('should work with React.createContext', async () => { - let defaultValue = 'default'; +describe("SSR", () => { + it("should work with React.createContext", async () => { + let defaultValue = "default"; let Context = React.createContext(defaultValue); - let providerValue = 'provider'; + let providerValue = "provider"; expect( await renderToStringWithData( - {val => { + {(val) => { expect(val).toBe(defaultValue); return val; }} @@ -123,7 +123,7 @@ describe('SSR', () => { await renderToStringWithData( - {val => { + {(val) => { expect(val).toBe(providerValue); return val; }} @@ -134,7 +134,7 @@ describe('SSR', () => { expect( await renderToStringWithData( - {val => { + {(val) => { expect(val).toBe(defaultValue); return val; }} @@ -147,34 +147,34 @@ describe('SSR', () => { await renderToStringWithData( - {val => { + {(val) => { expect(val).toBeUndefined(); - return val === undefined ? 'works' : 'broken'; + return val === undefined ? "works" : "broken"; }} ) - ).toBe('works'); + ).toBe("works"); const apolloClient = new ApolloClient({ - link: new ApolloLink(config => { - return new Observable(observer => { + link: new ApolloLink((config) => { + return new Observable((observer) => { execute({ schema: Schema, source: print(config.query), variableValues: config.variables, operationName: config.operationName, }) - .then(result => { + .then((result) => { observer.next(result); observer.complete(); }) - .catch(e => { + .catch((e) => { observer.error(e); }); }); }), - cache: new Cache() + cache: new Cache(), }); expect( @@ -192,7 +192,7 @@ describe('SSR', () => { > {() => ( - {val => { + {(val) => { expect(val).toBe(providerValue); return val; }} diff --git a/src/react/components/index.ts b/src/react/components/index.ts index 5e84197e074..5aa7ebb8a2e 100644 --- a/src/react/components/index.ts +++ b/src/react/components/index.ts @@ -1,5 +1,5 @@ -export { Query } from './Query.js'; -export { Mutation } from './Mutation.js'; -export { Subscription } from './Subscription.js'; +export { Query } from "./Query.js"; +export { Mutation } from "./Mutation.js"; +export { Subscription } from "./Subscription.js"; -export * from './types.js'; +export * from "./types.js"; diff --git a/src/react/components/types.ts b/src/react/components/types.ts index 000bbb2be03..4e1abacb6a1 100644 --- a/src/react/components/types.ts +++ b/src/react/components/types.ts @@ -1,7 +1,11 @@ -import type { DocumentNode } from 'graphql'; -import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; +import type { DocumentNode } from "graphql"; +import type { TypedDocumentNode } from "@graphql-typed-document-node/core"; -import type { OperationVariables, DefaultContext, ApolloCache } from '../../core/index.js'; +import type { + OperationVariables, + DefaultContext, + ApolloCache, +} from "../../core/index.js"; import type { QueryFunctionOptions, QueryResult, @@ -9,12 +13,12 @@ import type { MutationFunction, MutationResult, BaseSubscriptionOptions, - SubscriptionResult -} from '../types/types.js'; + SubscriptionResult, +} from "../types/types.js"; export interface QueryComponentOptions< TData = any, - TVariables extends OperationVariables = OperationVariables + TVariables extends OperationVariables = OperationVariables, > extends QueryFunctionOptions { children: (result: QueryResult) => JSX.Element | null; query: DocumentNode | TypedDocumentNode; @@ -24,7 +28,7 @@ export interface MutationComponentOptions< TData = any, TVariables = OperationVariables, TContext = DefaultContext, - TCache extends ApolloCache = ApolloCache + TCache extends ApolloCache = ApolloCache, > extends BaseMutationOptions { mutation: DocumentNode | TypedDocumentNode; children: ( @@ -35,7 +39,7 @@ export interface MutationComponentOptions< export interface SubscriptionComponentOptions< TData = any, - TVariables extends OperationVariables = OperationVariables + TVariables extends OperationVariables = OperationVariables, > extends BaseSubscriptionOptions { subscription: DocumentNode | TypedDocumentNode; children?: null | ((result: SubscriptionResult) => JSX.Element | null); diff --git a/src/react/context/ApolloConsumer.tsx b/src/react/context/ApolloConsumer.tsx index f5e12696cbd..ac26e734da9 100644 --- a/src/react/context/ApolloConsumer.tsx +++ b/src/react/context/ApolloConsumer.tsx @@ -1,15 +1,15 @@ -import { invariant } from '../../utilities/globals/index.js'; +import { invariant } from "../../utilities/globals/index.js"; -import * as React from 'react'; +import * as React from "react"; -import type { ApolloClient } from '../../core/index.js'; -import { getApolloContext } from './ApolloContext.js'; +import type { ApolloClient } from "../../core/index.js"; +import { getApolloContext } from "./ApolloContext.js"; export interface ApolloConsumerProps { children: (client: ApolloClient) => React.ReactChild | null; } -export const ApolloConsumer: React.FC = props => { +export const ApolloConsumer: React.FC = (props) => { const ApolloContext = getApolloContext(); return ( @@ -17,7 +17,7 @@ export const ApolloConsumer: React.FC = props => { invariant( context && context.client, 'Could not find "client" in the context of ApolloConsumer. ' + - 'Wrap the root component in an .' + "Wrap the root component in an ." ); return props.children(context.client); }} diff --git a/src/react/context/ApolloContext.ts b/src/react/context/ApolloContext.ts index d5d63b139a1..e942e8e9dad 100644 --- a/src/react/context/ApolloContext.ts +++ b/src/react/context/ApolloContext.ts @@ -1,8 +1,8 @@ -import * as React from 'react'; -import type { ApolloClient } from '../../core/index.js'; -import { canUseSymbol } from '../../utilities/index.js'; -import type { RenderPromises } from '../ssr/index.js'; -import { invariant } from '../../utilities/globals/index.js'; +import * as React from "react"; +import type { ApolloClient } from "../../core/index.js"; +import { canUseSymbol } from "../../utilities/index.js"; +import type { RenderPromises } from "../ssr/index.js"; +import { invariant } from "../../utilities/globals/index.js"; export interface ApolloContextValue { client?: ApolloClient; @@ -14,28 +14,30 @@ export interface ApolloContextValue { // in one context, then attempting to retrieve it from another different // context), a single Apollo context is created and tracked in global state. const contextKey = canUseSymbol - ? Symbol.for('__APOLLO_CONTEXT__') - : '__APOLLO_CONTEXT__'; + ? Symbol.for("__APOLLO_CONTEXT__") + : "__APOLLO_CONTEXT__"; export function getApolloContext(): React.Context { invariant( - 'createContext' in React, - 'Invoking `getApolloContext` in an environment where `React.createContext` is not available.\n' + - 'The Apollo Client functionality you are trying to use is only available in React Client Components.\n' + + "createContext" in React, + "Invoking `getApolloContext` in an environment where `React.createContext` is not available.\n" + + "The Apollo Client functionality you are trying to use is only available in React Client Components.\n" + 'Please make sure to add "use client" at the top of your file.\n' + // TODO: change to React documentation once React documentation contains information about Client Components - 'For more information, see https://nextjs.org/docs/getting-started/react-essentials#client-components' + "For more information, see https://nextjs.org/docs/getting-started/react-essentials#client-components" ); - - let context = (React.createContext as any)[contextKey] as React.Context; + + let context = (React.createContext as any)[ + contextKey + ] as React.Context; if (!context) { Object.defineProperty(React.createContext, contextKey, { - value: context = React.createContext({}), + value: (context = React.createContext({})), enumerable: false, writable: false, configurable: true, }); - context.displayName = 'ApolloContext'; + context.displayName = "ApolloContext"; } return context; } diff --git a/src/react/context/ApolloProvider.tsx b/src/react/context/ApolloProvider.tsx index 45028f37075..930e32dab0a 100644 --- a/src/react/context/ApolloProvider.tsx +++ b/src/react/context/ApolloProvider.tsx @@ -1,9 +1,9 @@ -import { invariant } from '../../utilities/globals/index.js'; +import { invariant } from "../../utilities/globals/index.js"; -import * as React from 'react'; +import * as React from "react"; -import type { ApolloClient } from '../../core/index.js'; -import { getApolloContext } from './ApolloContext.js'; +import type { ApolloClient } from "../../core/index.js"; +import { getApolloContext } from "./ApolloContext.js"; export interface ApolloProviderProps { client: ApolloClient; @@ -26,7 +26,7 @@ export const ApolloProvider: React.FC> = ({ invariant( context.client, - 'ApolloProvider was not passed a client instance. Make ' + + "ApolloProvider was not passed a client instance. Make " + 'sure you pass in your client via the "client" prop.' ); diff --git a/src/react/context/__tests__/ApolloConsumer.test.tsx b/src/react/context/__tests__/ApolloConsumer.test.tsx index c81c8a30358..aed5384c415 100644 --- a/src/react/context/__tests__/ApolloConsumer.test.tsx +++ b/src/react/context/__tests__/ApolloConsumer.test.tsx @@ -1,25 +1,25 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; +import React from "react"; +import { render, screen } from "@testing-library/react"; -import { ApolloLink } from '../../../link/core'; -import { ApolloClient } from '../../../core'; -import { InMemoryCache as Cache } from '../../../cache'; -import { ApolloProvider } from '../ApolloProvider'; -import { ApolloConsumer } from '../ApolloConsumer'; -import { getApolloContext } from '../ApolloContext'; -import { itAsync } from '../../../testing'; +import { ApolloLink } from "../../../link/core"; +import { ApolloClient } from "../../../core"; +import { InMemoryCache as Cache } from "../../../cache"; +import { ApolloProvider } from "../ApolloProvider"; +import { ApolloConsumer } from "../ApolloConsumer"; +import { getApolloContext } from "../ApolloContext"; +import { itAsync } from "../../../testing"; const client = new ApolloClient({ cache: new Cache(), - link: new ApolloLink((o, f) => (f ? f(o) : null)) + link: new ApolloLink((o, f) => (f ? f(o) : null)), }); -describe(' component', () => { - itAsync('has a render prop', (resolve, reject) => { +describe(" component", () => { + itAsync("has a render prop", (resolve, reject) => { render( - {clientRender => { + {(clientRender) => { try { expect(clientRender).toBe(client); resolve(); @@ -33,17 +33,17 @@ describe(' component', () => { ); }); - it('renders the content in the children prop', () => { + it("renders the content in the children prop", () => { render( {() =>
Test
}
); - expect(screen.getByText('Test')).toBeTruthy(); + expect(screen.getByText("Test")).toBeTruthy(); }); - it('errors if there is no client in the context', () => { + it("errors if there is no client in the context", () => { // Prevent Error about missing context type from appearing in the console. const errorLogger = console.error; console.error = () => {}; diff --git a/src/react/context/__tests__/ApolloProvider.test.tsx b/src/react/context/__tests__/ApolloProvider.test.tsx index 47d560558c7..7ed1fbdd576 100644 --- a/src/react/context/__tests__/ApolloProvider.test.tsx +++ b/src/react/context/__tests__/ApolloProvider.test.tsx @@ -1,13 +1,13 @@ -import React, { useContext } from 'react'; -import { render, screen } from '@testing-library/react'; +import React, { useContext } from "react"; +import { render, screen } from "@testing-library/react"; -import { ApolloLink } from '../../../link/core'; -import { ApolloClient } from '../../../core'; -import { InMemoryCache as Cache } from '../../../cache'; -import { ApolloProvider, ApolloProviderProps } from '../ApolloProvider'; -import { ApolloContextValue, getApolloContext } from '../ApolloContext'; +import { ApolloLink } from "../../../link/core"; +import { ApolloClient } from "../../../core"; +import { InMemoryCache as Cache } from "../../../cache"; +import { ApolloProvider, ApolloProviderProps } from "../ApolloProvider"; +import { ApolloContextValue, getApolloContext } from "../ApolloContext"; -describe(' Component', () => { +describe(" Component", () => { const client = new ApolloClient({ cache: new Cache(), link: new ApolloLink((o, f) => (f ? f(o) : null)), @@ -18,27 +18,27 @@ describe(' Component', () => { link: new ApolloLink((o, f) => (f ? f(o) : null)), }); - it('should render children components', () => { + it("should render children components", () => { render(
Test
); - expect(screen.getByText('Test')).toBeTruthy(); + expect(screen.getByText("Test")).toBeTruthy(); }); - it('should support the 2.0', () => { + it("should support the 2.0", () => { render( }>
Test
); - expect(screen.getByText('Test')).toBeTruthy(); + expect(screen.getByText("Test")).toBeTruthy(); }); - it('should require a client', () => { + it("should require a client", () => { const originalConsoleError = console.error; console.error = () => { /* noop */ @@ -55,22 +55,22 @@ describe(' Component', () => { ); }).toThrowError( - 'ApolloProvider was not passed a client instance. Make ' + + "ApolloProvider was not passed a client instance. Make " + 'sure you pass in your client via the "client" prop.' ); console.error = originalConsoleError; }); - it('should not require a store', () => { + it("should not require a store", () => { render(
Test
); - expect(screen.getByText('Test')).toBeTruthy(); + expect(screen.getByText("Test")).toBeTruthy(); }); - it('should add the client to the children context', () => { + it("should add the client to the children context", () => { const TestChild = () => { const context = useContext(getApolloContext()); expect(context.client).toEqual(client); @@ -84,7 +84,7 @@ describe(' Component', () => { ); }); - it('should update props when the client changes', () => { + it("should update props when the client changes", () => { let clientToCheck = client; const TestChild = () => { @@ -113,64 +113,65 @@ describe(' Component', () => { describe.each< [ string, - Omit, 'children'>, - Omit, 'children'> + Omit, "children">, + Omit, "children">, ] - >([ - ['client', { client }, { client: anotherClient }], - ])('context value stability, %s prop', (prop, value, childValue) => { - it(`should not recreate the context value if the ${prop} prop didn't change`, () => { - let lastContext: ApolloContextValue | undefined; - - const TestChild = () => { - lastContext = useContext(getApolloContext()); - return null; - }; - - const { rerender } = render( - - - - ); + >([["client", { client }, { client: anotherClient }]])( + "context value stability, %s prop", + (prop, value, childValue) => { + it(`should not recreate the context value if the ${prop} prop didn't change`, () => { + let lastContext: ApolloContextValue | undefined; + + const TestChild = () => { + lastContext = useContext(getApolloContext()); + return null; + }; + + const { rerender } = render( + + + + ); - const firstContextValue = lastContext; + const firstContextValue = lastContext; - rerender( - - - - ); + rerender( + + + + ); - expect(lastContext).toBe(firstContextValue); - }); + expect(lastContext).toBe(firstContextValue); + }); - it(`should not recreate the context if the parent context value differs, but the ${prop} prop didn't change`, () => { - let lastContext: ApolloContextValue | undefined; + it(`should not recreate the context if the parent context value differs, but the ${prop} prop didn't change`, () => { + let lastContext: ApolloContextValue | undefined; - const TestChild = () => { - lastContext = useContext(getApolloContext()); - return null; - }; + const TestChild = () => { + lastContext = useContext(getApolloContext()); + return null; + }; - const { rerender } = render( - - - + const { rerender } = render( + + + + - - ); + ); - const firstContextValue = lastContext; + const firstContextValue = lastContext; - rerender( - - - + rerender( + + + + - - ); + ); - expect(lastContext).toBe(firstContextValue); - }); - }); + expect(lastContext).toBe(firstContextValue); + }); + } + ); }); diff --git a/src/react/context/index.ts b/src/react/context/index.ts index 40488fd58fe..18cc35a1bcf 100644 --- a/src/react/context/index.ts +++ b/src/react/context/index.ts @@ -1,9 +1,9 @@ -import '../../utilities/globals/index.js'; +import "../../utilities/globals/index.js"; -export { ApolloConsumer, ApolloConsumerProps } from './ApolloConsumer.js'; +export { ApolloConsumer, ApolloConsumerProps } from "./ApolloConsumer.js"; export { ApolloContextValue, getApolloContext, - resetApolloContext -} from './ApolloContext.js'; -export { ApolloProvider, ApolloProviderProps } from './ApolloProvider.js'; + resetApolloContext, +} from "./ApolloContext.js"; +export { ApolloProvider, ApolloProviderProps } from "./ApolloProvider.js"; diff --git a/src/react/hoc/__tests__/client-option.test.tsx b/src/react/hoc/__tests__/client-option.test.tsx index 2d9587d04fb..b6efd611d09 100644 --- a/src/react/hoc/__tests__/client-option.test.tsx +++ b/src/react/hoc/__tests__/client-option.test.tsx @@ -1,17 +1,17 @@ -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', () => { +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) { @@ -21,22 +21,22 @@ describe('client option', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; type Data = typeof data; const link = mockSingleLink({ request: { query }, - result: { data } + result: { data }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const config = { options: { - client - } + client, + }, }; const ContainerWithData = graphql<{}, Data>(query, config)(() => null); const { unmount } = render( @@ -44,7 +44,7 @@ describe('client option', () => { client={ new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }) } > @@ -54,7 +54,7 @@ describe('client option', () => { unmount(); }); - itAsync('doesnt require a recycler', (resolve, reject) => { + itAsync("doesnt require a recycler", (resolve, reject) => { const query = gql` query people { allPeople(first: 1) { @@ -64,24 +64,27 @@ describe('client option', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; type Data = typeof data; const link = mockSingleLink({ request: { query }, - result: { data } + result: { data }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const config = { options: { - client - } + client, + }, }; - let renderCount = 0 - const ContainerWithData = graphql<{}, Data>(query, config)(() => { + let renderCount = 0; + const ContainerWithData = graphql<{}, Data>( + query, + config + )(() => { renderCount += 1; return null; }); @@ -92,72 +95,77 @@ describe('client option', () => { }).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 + 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 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); } - 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) => { + itAsync("exposes refetch as part of the props api", (resolve, reject) => { let done = false; const query: DocumentNode = gql` query people($first: Int) { @@ -171,16 +179,16 @@ describe('client option', () => { const variables = { first: 1 }; type Variables = typeof variables; - const data1 = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; type Data = typeof data1; const link = mockSingleLink({ request: { query, variables }, - result: { data: data1 } + result: { data: data1 }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const Container = graphql(query)( diff --git a/src/react/hoc/__tests__/fragments.test.tsx b/src/react/hoc/__tests__/fragments.test.tsx index e492f1dafa7..1597a4b7092 100644 --- a/src/react/hoc/__tests__/fragments.test.tsx +++ b/src/react/hoc/__tests__/fragments.test.tsx @@ -1,18 +1,18 @@ -import React from 'react'; -import { render, waitFor } from '@testing-library/react'; -import gql from 'graphql-tag'; -import { DocumentNode } from 'graphql'; +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'; +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', () => { +describe("fragments", () => { // XXX in a later version, we should support this for composition - it('throws if you only pass a fragment', () => { + it("throws if you only pass a fragment", () => { const query: DocumentNode = gql` fragment Failure on PeopleConnection { people { @@ -21,17 +21,17 @@ describe('fragments', () => { } `; const expectedData = { - allPeople: { people: [{ name: 'Luke Skywalker' }] } + allPeople: { people: [{ name: "Luke Skywalker" }] }, }; type Data = typeof expectedData; const link = mockSingleLink({ request: { query }, - result: { data: expectedData } + result: { data: expectedData }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); try { @@ -40,9 +40,7 @@ describe('fragments', () => { componentDidUpdate() { const { props } = this; expect(props.data!.loading).toBeFalsy(); - expect(props.data!.allPeople).toEqual( - expectedData.allPeople - ); + expect(props.data!.allPeople).toEqual(expectedData.allPeople); } render() { return null; @@ -61,63 +59,64 @@ describe('fragments', () => { } }); - itAsync('correctly fetches a query with inline fragments', (resolve, reject) => { - let done = false; - const query: DocumentNode = gql` - query people { - allPeople(first: 1) { - __typename - ...person + 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 + fragment person on PeopleConnection { + people { + name + } } - } - `; - const data = { - allPeople: { - __typename: 'PeopleConnection', - people: [{ name: 'Luke Skywalker' }] - } - }; + `; + const data = { + allPeople: { + __typename: "PeopleConnection", + people: [{ name: "Luke Skywalker" }], + }, + }; - type Data = typeof data; + type Data = typeof data; - const link = mockSingleLink({ - request: { query }, - result: { data } - }); - const client = new ApolloClient({ - link, - cache: new Cache({ addTypename: false }) - }); + 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; + 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( - - - - ); + render( + + + + ); - waitFor(() => { - expect(done).toBe(true); - }).then(resolve, reject); - }); + 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 index f316dbe43c5..7f1ba109205 100644 --- a/src/react/hoc/__tests__/mutations/index.test.tsx +++ b/src/react/hoc/__tests__/mutations/index.test.tsx @@ -1,18 +1,14 @@ -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'; +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 { @@ -35,10 +31,10 @@ interface Variables { } const expectedData = { - allPeople: { people: [{ name: 'Luke Skywalker' }] } + allPeople: { people: [{ name: "Luke Skywalker" }] }, }; -describe('graphql(mutation)', () => { +describe("graphql(mutation)", () => { let error: typeof console.error; let client: ApolloClient; beforeEach(() => { @@ -51,12 +47,12 @@ describe('graphql(mutation)', () => { console.error = error; }); - it('binds a mutation to props', () => { + 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'); + expect(typeof mutate).toBe("function"); + expect(typeof result).toBe("object"); return null; }); @@ -67,7 +63,7 @@ describe('graphql(mutation)', () => { ); }); - it('binds a mutation result to props', () => { + it("binds a mutation result to props", () => { type InjectedProps = { result: any; }; @@ -77,7 +73,7 @@ describe('graphql(mutation)', () => { )(({ result }) => { const { loading, error } = result; expect(result).toBeTruthy(); - expect(typeof loading).toBe('boolean'); + expect(typeof loading).toBe("boolean"); expect(error).toBeFalsy(); return null; @@ -90,7 +86,7 @@ describe('graphql(mutation)', () => { ); }); - it('binds a mutation to props with a custom name', () => { + it("binds a mutation to props with a custom name", () => { interface Props {} type InjectedProps = { @@ -100,12 +96,12 @@ describe('graphql(mutation)', () => { const ContainerWithData = graphql( query, - { name: 'customMutation' } + { name: "customMutation" } )(({ customMutation, customMutationResult }) => { expect(customMutation).toBeTruthy(); expect(customMutationResult).toBeTruthy(); - expect(typeof customMutation).toBe('function'); - expect(typeof customMutationResult).toBe('object'); + expect(typeof customMutation).toBe("function"); + expect(typeof customMutationResult).toBe("object"); return null; }); @@ -116,7 +112,7 @@ describe('graphql(mutation)', () => { ); }); - it('binds a mutation to custom props', () => { + it("binds a mutation to custom props", () => { interface Props { methodName: string; } @@ -128,12 +124,12 @@ describe('graphql(mutation)', () => { { props: ({ ownProps, mutate: addPerson }) => ({ [ownProps.methodName]: (name: string) => - addPerson!({ variables: { name } }) - }) + addPerson!({ variables: { name } }), + }), } )(({ myInjectedMutationMethod }) => { expect(myInjectedMutationMethod).toBeTruthy(); - expect(typeof myInjectedMutationMethod).toBe('function'); + expect(typeof myInjectedMutationMethod).toBe("function"); return null; }); @@ -144,7 +140,7 @@ describe('graphql(mutation)', () => { ); }); - itAsync('does not swallow children errors', (resolve, reject) => { + itAsync("does not swallow children errors", (resolve, reject) => { let bar: any; const ContainerWithData = graphql(query)(() => { bar(); // this will throw @@ -173,11 +169,11 @@ describe('graphql(mutation)', () => { ); }); - itAsync('can execute a mutation', (resolve, reject) => { + itAsync("can execute a mutation", (resolve, reject) => { const Container = graphql(query)( class extends React.Component { componentDidMount() { - this.props.mutate!().then(result => { + this.props.mutate!().then((result) => { expect(result && result.data).toEqual(expectedData); resolve(); }); @@ -195,95 +191,101 @@ describe('graphql(mutation)', () => { ); }); - itAsync('can execute a mutation with variables from props', (resolve, reject) => { - const queryWithVariables = gql` - mutation addPerson($first: Int) { - allPeople(first: $first) { - people { - name + 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; - } + `; + client = createMockClient(expectedData, queryWithVariables, { + first: 1, + }); - const Container = graphql(queryWithVariables)( - class extends React.Component> { - componentDidMount() { - this.props.mutate!().then(result => { - expect(result && result.data).toEqual(expectedData); - resolve(); - }); - } - render() { - return null; - } + interface Props { + first: number; } - ); - 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 Container = graphql(queryWithVariables)( + class extends React.Component> { + componentDidMount() { + this.props.mutate!().then((result) => { + expect(result && result.data).toEqual(expectedData); + resolve(); + }); + } + render() { + return null; } } - } - `; + ); - const mocks = [ - { - request: { - query: queryWithVariables, - variables: { - first: 1, - second: 2 + 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 + } } - }, - 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; + `; + + 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( - - - - ); - }); + render( + + + + ); + } + ); }); diff --git a/src/react/hoc/__tests__/mutations/lifecycle.test.tsx b/src/react/hoc/__tests__/mutations/lifecycle.test.tsx index 3aa0a8db3ac..0c0b88efd8e 100644 --- a/src/react/hoc/__tests__/mutations/lifecycle.test.tsx +++ b/src/react/hoc/__tests__/mutations/lifecycle.test.tsx @@ -1,11 +1,11 @@ -import React from 'react'; -import { render, cleanup } from '@testing-library/react'; -import gql from 'graphql-tag'; +import React from "react"; +import { render, cleanup } 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'; +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) { @@ -17,40 +17,43 @@ const query = gql` } `; const expectedData = { - allPeople: { people: [{ name: 'Luke Skywalker' }] } + allPeople: { people: [{ name: "Luke Skywalker" }] }, }; -describe('graphql(mutation) lifecycle', () => { +describe("graphql(mutation) lifecycle", () => { afterEach(cleanup); - itAsync('allows falsy values in the mapped variables from props', (resolve, reject) => { - const client = createMockClient(expectedData, query, { id: null }); + 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(); - }); - } + interface Props { + id: string | null; + } - render() { - return 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( - - - - ); - }); + render( + + + + ); + } + ); it("errors if the passed props don't contain the needed variables", () => { const client = createMockClient(expectedData, query, { first: 1 }); @@ -69,53 +72,56 @@ describe('graphql(mutation) lifecycle', () => { } }); - itAsync('rebuilds the mutation on prop change when using `options`', (resolve, reject) => { - const client = createMockClient(expectedData, query, { - id: 2 - }); + itAsync( + "rebuilds the mutation on prop change when using `options`", + (resolve, reject) => { + const client = createMockClient(expectedData, query, { + id: 2, + }); - interface Props { - listId: number; - } + interface Props { + listId: number; + } - function options(props: Props) { - return { - variables: { - id: props.listId - } - }; - } + function options(props: Props) { + return { + variables: { + id: props.listId, + }, + }; + } - class Container extends React.Component> { - render() { - if (this.props.listId !== 2) return null; - this.props.mutate!().then(() => resolve()); - return null; + class Container extends React.Component> { + render() { + if (this.props.listId !== 2) return null; + this.props.mutate!().then(() => resolve()); + return null; + } } - } - const ContainerWithMutate = graphql(query, { options })(Container); + const ContainerWithMutate = graphql(query, { options })(Container); - class ChangingProps extends React.Component<{}, { listId: number }> { - state = { listId: 1 }; + class ChangingProps extends React.Component<{}, { listId: number }> { + state = { listId: 1 }; - componentDidMount() { - setTimeout(() => this.setState({ listId: 2 }), 50); - } + componentDidMount() { + setTimeout(() => this.setState({ listId: 2 }), 50); + } - render() { - return ; + render() { + return ; + } } - } - render( - - - - ); - }); + render( + + + + ); + } + ); - itAsync('can execute a mutation with custom variables', (resolve, reject) => { + itAsync("can execute a mutation with custom variables", (resolve, reject) => { const client = createMockClient(expectedData, query, { id: 1 }); interface Variables { id: number; @@ -124,7 +130,7 @@ describe('graphql(mutation) lifecycle', () => { const Container = graphql<{}, {}, Variables>(query)( class extends React.Component> { componentDidMount() { - this.props.mutate!({ variables: { id: 1 } }).then(result => { + this.props.mutate!({ variables: { id: 1 } }).then((result) => { expect(result && result.data).toEqual(expectedData); resolve(); }); diff --git a/src/react/hoc/__tests__/mutations/queries.test.tsx b/src/react/hoc/__tests__/mutations/queries.test.tsx index d3b31512b41..2144a974e2a 100644 --- a/src/react/hoc/__tests__/mutations/queries.test.tsx +++ b/src/react/hoc/__tests__/mutations/queries.test.tsx @@ -1,88 +1,91 @@ -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 React from "react"; +import { act, render, waitFor } from "@testing-library/react"; +import gql from "graphql-tag"; +import { DocumentNode } from "graphql"; + 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 + 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 } - __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 - } - }; + `; + + const data = { + __typename: "Mutation", + createTodo: { + __typename: "Todo", + id: "99", + text: "This one was created with a mutation.", + completed: true, + }, + }; - this.props.mutate!({ optimisticResponse }).then(result => { - expect(result && result.data).toEqual(data); - mutateFired = 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; + const dataInStore = client.cache.extract(true); + expect(dataInStore["Todo:99"]).toEqual( + optimisticResponse.createTodo + ); + } + render() { + return null; + } } - } - ); + ); - render( - - - - ); + render( + + + + ); - waitFor(() => { - expect(mutateFired).toBeTruthy(); - }).then(resolve, reject); - }); + waitFor(() => { + expect(mutateFired).toBeTruthy(); + }).then(resolve, reject); + } + ); - itAsync('allows for updating queries from a mutation', (resolve, reject) => { + itAsync("allows for updating queries from a mutation", (resolve, reject) => { const query: DocumentNode = gql` query todos { todo_list { @@ -109,20 +112,20 @@ describe('graphql(mutation) query integration', () => { const mutationData = { createTodo: { - id: '99', - text: 'This one was created with a mutation.', - completed: true - } + 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 - } + id: "99", + text: "Optimistically generated", + completed: true, + }, }; interface QueryData { todo_list: { @@ -146,13 +149,13 @@ describe('graphql(mutation) query integration', () => { }; const expectedData = { - todo_list: { id: '123', title: 'how to apollo', tasks: [] } + todo_list: { id: "123", title: "how to apollo", tasks: [] }, }; const link = mockSingleLink( { request: { query }, - result: { data: expectedData } + result: { data: expectedData }, }, { request: { query: mutation }, result: { data: mutationData } } ); @@ -163,7 +166,7 @@ describe('graphql(mutation) query integration', () => { type WithQueryChildProps = ChildProps<{}, QueryData>; const withMutation = graphql(mutation, { - options: () => ({ optimisticResponse, update }) + options: () => ({ optimisticResponse, update }), }); let renderCount = 0; @@ -174,7 +177,7 @@ describe('graphql(mutation) query integration', () => { 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 => { + this.props.mutate!().then((result) => { expect(result && result.data).toEqual(mutationData); }); }); @@ -184,12 +187,12 @@ describe('graphql(mutation) query integration', () => { switch (++renderCount) { case 1: expect(this.props.data.todo_list.tasks).toEqual([ - optimisticResponse.createTodo + optimisticResponse.createTodo, ]); break; case 2: expect(this.props.data.todo_list.tasks).toEqual([ - mutationData.createTodo + mutationData.createTodo, ]); break; default: @@ -213,135 +216,134 @@ describe('graphql(mutation) query integration', () => { }).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) + 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' - } - }; + `; + + const queryData = { + mini: { + id: 1, + __typename: "Mini", + cover: "image1", + }, + }; - type Data = typeof queryData; + type Data = typeof queryData; - const variables = { id: 1 }; + const variables = { id: 1 }; - type Variables = typeof variables; + type Variables = typeof variables; - const mutation: DocumentNode = gql` - mutation($signature: String!) { - mini: submitMiniCoverS3DirectUpload(signature: $signature) { - __typename - id - cover(maxWidth: 600, maxHeight: 400) + 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; + `; + + const mutationData = { + mini: { + id: 1, + cover: "image2", + __typename: "Mini", + }, + }; - interface MutationVariables { - signature: string; - } + type MutationData = typeof mutationData; - const link = mockSingleLink( - { request: { query, variables }, result: { data: queryData } }, - { - request: { query: mutation, variables: { signature: '1233' } }, - result: { data: mutationData } + interface MutationVariables { + signature: string; } - ); - 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; - } - } + 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 }); - let count = 0; - const MutationContainer = graphql( - mutation - )( - class extends React.Component< - ChildProps - > { + class Boundary extends React.Component { + componentDidCatch(e: any) { + reject(e); + } render() { - if (count === 1) { - setTimeout(() => { - this.props.mutate!() - .then(result => { - expect(result && result.data).toEqual( - mutationData - ); - }) - .catch(reject); - }); - } - return null; + // eslint-disable-next-line testing-library/no-node-access + return this.props.children; } } - ); - const Container = graphql(query)( - class extends React.Component> { - render() { - if (count === 1) { - expect(this.props.data!.mini).toEqual(queryData.mini); + 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; } - if (count === 2) { - expect(this.props.data!.mini).toEqual( - mutationData.mini + } + ); + + 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 ( + ); } - count++; - - return ( - - ); } - } - ); + ); - render( - - - - - - ); + render( + + + + + + ); - waitFor(() => { - expect(count).toBe(3); - }).then(resolve, reject); - }); + waitFor(() => { + expect(count).toBe(3); + }).then(resolve, reject); + } + ); - it('should be able to override the internal `ignoreResults` setting', async () => { + it("should be able to override the internal `ignoreResults` setting", async () => { const mutation: DocumentNode = gql` - mutation($signature: String!) { + mutation ($signature: String!) { mini: submitMiniCoverS3DirectUpload(signature: $signature) { __typename id @@ -353,9 +355,9 @@ describe('graphql(mutation) query integration', () => { const mutationData = { mini: { id: 1, - cover: 'image2', - __typename: 'Mini' - } + cover: "image2", + __typename: "Mini", + }, }; type MutationData = typeof mutationData; @@ -365,8 +367,8 @@ describe('graphql(mutation) query integration', () => { } const link = mockSingleLink({ - request: { query: mutation, variables: { signature: '1233' } }, - result: { data: mutationData } + request: { query: mutation, variables: { signature: "1233" } }, + result: { data: mutationData }, }); const cache = new Cache({ addTypename: false }); @@ -385,10 +387,8 @@ describe('graphql(mutation) query integration', () => { case 0: expect(this.props.result!.loading).toBeFalsy(); setTimeout(() => { - this.props.mutate!().then(result => { - expect(result && result.data).toEqual( - mutationData - ); + this.props.mutate!().then((result) => { + expect(result && result.data).toEqual(mutationData); }); }); break; diff --git a/src/react/hoc/__tests__/mutations/recycled-queries.test.tsx b/src/react/hoc/__tests__/mutations/recycled-queries.test.tsx index 4b2eb96b78d..67f3403006b 100644 --- a/src/react/hoc/__tests__/mutations/recycled-queries.test.tsx +++ b/src/react/hoc/__tests__/mutations/recycled-queries.test.tsx @@ -1,17 +1,21 @@ -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', () => { +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. @@ -31,7 +35,7 @@ describe('graphql(mutation) update queries', () => { // // 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 () => { + it("will run `update` for a previously mounted component", async () => { const query: DocumentNode = gql` query todos { todo_list { @@ -66,10 +70,10 @@ describe('graphql(mutation) update queries', () => { const mutationData = { createTodo: { - id: '99', - text: 'This one was created with a mutation.', - completed: true - } + id: "99", + text: "This one was created with a mutation.", + completed: true, + }, }; type MutationData = typeof mutationData; @@ -82,35 +86,33 @@ describe('graphql(mutation) update queries', () => { > = (proxy, result) => { todoUpdateQueryCount++; const data = JSON.parse( - JSON.stringify( - proxy.readQuery({ query }) - ) // read from cache + 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: [] } + todo_list: { id: "123", title: "how to apollo", tasks: [] }, }; const link = mockSingleLink( { request: { query }, - result: { data: expectedData } + 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 }) + cache: new Cache({ addTypename: false }), }); let mutate: MutationFunction; const MyMutation = graphql<{}, MutationData>(mutation, { - options: () => ({ update }) + options: () => ({ update }), })( class extends React.Component> { componentDidMount() { @@ -148,48 +150,48 @@ describe('graphql(mutation) update queries', () => { case 1: expect(this.props.data!.loading).toBeFalsy(); expect(this.props.data!.todo_list).toEqual({ - id: '123', - title: 'how to apollo', - tasks: [] + 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', + 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; 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', + 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, }, { - 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") + throw new Error("too many rerenders"); } } catch (e) { testFailures.push(e); @@ -214,13 +216,19 @@ describe('graphql(mutation) update queries', () => { ); let resolveLastTimeout: () => void; - const allTimeoutsFinished = new Promise(r => { resolveLastTimeout = r }); + const allTimeoutsFinished = new Promise((r) => { + resolveLastTimeout = r; + }); const catchingSetTimeout = (cb: (args: void) => void, ms: number) => { return setTimeout(() => { - try { cb() } catch (e) { testFailures.push(e) } + try { + cb(); + } catch (e) { + testFailures.push(e); + } }, ms); - } + }; catchingSetTimeout(() => { mutate(); @@ -263,7 +271,7 @@ describe('graphql(mutation) update queries', () => { } }); - it('will run `refetchQueries` for a recycled queries', async () => { + it("will run `refetchQueries` for a recycled queries", async () => { const mutation: DocumentNode = gql` mutation createTodo { createTodo { @@ -276,10 +284,10 @@ describe('graphql(mutation) update queries', () => { const mutationData = { createTodo: { - id: '99', - text: 'This one was created with a mutation.', - completed: true - } + id: "99", + text: "This one was created with a mutation.", + completed: true, + }, }; type MutationData = typeof mutationData; @@ -311,28 +319,28 @@ describe('graphql(mutation) update queries', () => { } const data = { - todo_list: { id: '123', title: 'how to apollo', tasks: [] } + todo_list: { id: "123", title: "how to apollo", tasks: [] }, }; const updatedData = { todo_list: { - id: '123', - title: 'how to apollo', - tasks: [mutationData.createTodo] - } + id: "123", + title: "how to apollo", + tasks: [mutationData.createTodo], + }, }; const link = mockSingleLink( - { request: { query, variables: { id: '123' } }, result: { data } }, + { request: { query, variables: { id: "123" } }, result: { data } }, { request: { query: mutation }, result: { data: mutationData } }, { - request: { query, variables: { id: '123' } }, - result: { data: updatedData } + request: { query, variables: { id: "123" } }, + result: { data: updatedData }, } ); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let mutate: MutationFunction; @@ -369,23 +377,19 @@ describe('graphql(mutation) update queries', () => { case 1: expect(this.props.data!.loading).toBeFalsy(); expect(this.props.data!.todo_list).toEqual({ - id: '123', - title: 'how to apollo', - tasks: [] + 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 - ); + 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 - ); + expect(this.props.data!.todo_list).toEqual(updatedData.todo_list); break; default: } @@ -408,7 +412,7 @@ describe('graphql(mutation) update queries', () => { ); setTimeout(() => { - mutate({ refetchQueries: [{ query, variables: { id: '123' } }] }).then( + mutate({ refetchQueries: [{ query, variables: { id: "123" } }] }).then( () => { setTimeout(() => { render( diff --git a/src/react/hoc/__tests__/queries/api.test.tsx b/src/react/hoc/__tests__/queries/api.test.tsx index a37d5a1b200..d8b0da578e5 100644 --- a/src/react/hoc/__tests__/queries/api.test.tsx +++ b/src/react/hoc/__tests__/queries/api.test.tsx @@ -1,16 +1,16 @@ -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', () => { +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; @@ -20,7 +20,7 @@ describe('[queries] api', () => { console.warn = consoleWarn; }); - itAsync('exposes refetch as part of the props api', (resolve, reject) => { + itAsync("exposes refetch as part of the props api", (resolve, reject) => { const query: DocumentNode = gql` query people($first: Int) { allPeople(first: $first) { @@ -31,7 +31,7 @@ describe('[queries] api', () => { } `; const variables = { first: 1 }; - const data1 = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; const link = mockSingleLink( { request: { query, variables }, result: { data: data1 } }, { request: { query, variables }, result: { data: data1 } }, @@ -39,7 +39,7 @@ describe('[queries] api', () => { ); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let hasRefetched = false, @@ -76,9 +76,7 @@ describe('[queries] api', () => { .refetch({ first: 2 }) // new variables .then((response: any) => { expect(response.data).toEqual(data1); - expect(data!.allPeople).toEqual( - data1.allPeople - ); + expect(data!.allPeople).toEqual(data1.allPeople); done = true; }); }) @@ -105,51 +103,56 @@ describe('[queries] api', () => { 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 + 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; + `; + 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; } - return null; } - } - ); + ); - render( - - - - ); + render( + + + + ); - waitFor(() => expect(done).toBeTruthy()).then(resolve, reject); - }); + waitFor(() => expect(done).toBeTruthy()).then(resolve, reject); + } + ); - itAsync('exposes fetchMore as part of the props api', (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) { @@ -159,8 +162,8 @@ describe('[queries] api', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; - const data1 = { allPeople: { people: [{ name: 'Leia Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; + const data1 = { allPeople: { people: [{ name: "Leia Skywalker" }] } }; type Data = typeof data; @@ -175,12 +178,12 @@ describe('[queries] api', () => { ); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let count = 0; const Container = graphql<{}, Data, Variables>(query, { - options: () => ({ variables }) + options: () => ({ variables }), })( class extends React.Component> { componentDidUpdate() { @@ -188,16 +191,16 @@ describe('[queries] api', () => { if (count === 0) { expect(props.data!.fetchMore).toBeTruthy(); expect(props.data!.fetchMore instanceof Function).toBeTruthy(); - props.data! - .fetchMore({ + 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( @@ -214,7 +217,7 @@ describe('[queries] api', () => { // This ends the test (passing). setTimeout(() => resolve(), 20); } else { - throw new Error('should not reach this point'); + throw new Error("should not reach this point"); } count++; } @@ -234,100 +237,101 @@ describe('[queries] api', () => { ); }); - 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 + 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] - } - }; + `; + 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!(); } - }) - }; - } - })( - 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; + return null; + } } - } - ); + ); - render( - - - - ); + render( + + + + ); - waitFor(() => expect(done).toBe(true)).then(resolve, reject); - }); + 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 index bcf3822e2cd..449e3c46c67 100644 --- a/src/react/hoc/__tests__/queries/errors.test.tsx +++ b/src/react/hoc/__tests__/queries/errors.test.tsx @@ -1,19 +1,19 @@ -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', () => { +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; @@ -24,7 +24,7 @@ describe('[queries] errors', () => { }); // errors - itAsync('does not swallow children errors', (resolve, reject) => { + itAsync("does not swallow children errors", (resolve, reject) => { let done = false; const query: DocumentNode = gql` query people { @@ -35,14 +35,14 @@ describe('[queries] errors', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; const link = mockSingleLink({ request: { query }, - result: { data } + result: { data }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); class ErrorBoundary extends React.Component { @@ -75,7 +75,7 @@ describe('[queries] errors', () => { }).then(resolve, reject); }); - it('can unmount without error', () => { + it("can unmount without error", () => { const query: DocumentNode = gql` query people { allPeople(first: 1) { @@ -85,14 +85,14 @@ describe('[queries] errors', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; const link = mockSingleLink({ request: { query }, - result: { data } + result: { data }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const ContainerWithData = graphql(query)(() => null); @@ -110,8 +110,8 @@ describe('[queries] errors', () => { } }); - itAsync('passes any GraphQL errors in props', (resolve, reject) => { - let done = false + itAsync("passes any GraphQL errors in props", (resolve, reject) => { + let done = false; const query: DocumentNode = gql` query people { allPeople(first: 1) { @@ -123,11 +123,11 @@ describe('[queries] errors', () => { `; const link = mockSingleLink({ request: { query }, - error: new Error('boo') + error: new Error("boo"), }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const ErrorContainer = graphql(query)( @@ -136,7 +136,7 @@ describe('[queries] errors', () => { const { data } = this.props; expect(data!.error).toBeTruthy(); expect(data!.error!.networkError).toBeTruthy(); - done = true + done = true; } render() { return null; @@ -155,7 +155,7 @@ describe('[queries] errors', () => { }).then(resolve, reject); }); - describe('uncaught exceptions', () => { + describe("uncaught exceptions", () => { const consoleWarn = console.warn; beforeAll(() => { console.warn = () => null; @@ -171,13 +171,13 @@ describe('[queries] errors', () => { } beforeEach(() => { unhandled = []; - process.on('unhandledRejection', handle); + process.on("unhandledRejection", handle); }); afterEach(() => { - process.removeListener('unhandledRejection', handle); + process.removeListener("unhandledRejection", handle); }); - it('does not log when you change variables resulting in an error', async () => { + it("does not log when you change variables resulting in an error", async () => { const query: DocumentNode = gql` query people($var: Int) { allPeople(first: $var) { @@ -188,21 +188,21 @@ describe('[queries] errors', () => { } `; const var1 = { var: 1 }; - const data = { allPeople: { people: { name: 'Luke Skywalker' } } }; + const data = { allPeople: { people: { name: "Luke Skywalker" } } }; const var2 = { var: 2 }; const link = mockSingleLink( { request: { query, variables: var1 }, - result: { data } + result: { data }, }, { request: { query, variables: var2 }, - error: new Error('boo') + error: new Error("boo"), } ); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); type Data = typeof data; @@ -215,7 +215,11 @@ describe('[queries] errors', () => { let iteration = 0; let done = false; - const ErrorContainer = withState('var', 'setVar', 1)( + const ErrorContainer = withState( + "var", + "setVar", + 1 + )( graphql(query)( class extends React.Component> { componentDidUpdate() { @@ -258,14 +262,14 @@ describe('[queries] errors', () => { 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', () => + 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 { @@ -285,11 +289,11 @@ describe('[queries] errors', () => { const link = mockSingleLink({ request: { query }, - error: new Error('oops') + error: new Error("oops"), }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const origError = console.error; @@ -313,7 +317,7 @@ describe('[queries] errors', () => { expect(errorMock.mock.calls.length).toBe(0); break; default: - throw new Error('Too many renders.'); + throw new Error("Too many renders."); } } catch (error) { reject(error); @@ -335,83 +339,80 @@ describe('[queries] errors', () => { }).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 + 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 }) - }); + `; + 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'); + 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); } - } catch (e) { - reject(e); } - } - render() { - return null; + render() { + return null; + } } - } - ); + ); - render( - - - - ); + render( + + + + ); - waitFor(() => { - expect(count).toBe(3) - }).then(resolve, reject); - }); + waitFor(() => { + expect(count).toBe(3); + }).then(resolve, reject); + } + ); - itAsync('can refetch after there was a network error', (resolve, reject) => { + itAsync("can refetch after there was a network error", (resolve, reject) => { const query: DocumentNode = gql` query somethingelse { allPeople(first: 1) { @@ -421,24 +422,24 @@ describe('[queries] errors', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; - const dataTwo = { allPeople: { people: [{ name: 'Princess Leia' }] } }; + 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 }, error: new Error("This is an error!") }, { request: { query }, result: { data: dataTwo } } ); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let count = 0; const noop = () => null; const Container = graphql<{}, Data>(query, { - options: { notifyOnNetworkStatusChange: true } + options: { notifyOnNetworkStatusChange: true }, })( class extends React.Component> { componentDidUpdate() { @@ -449,7 +450,7 @@ describe('[queries] errors', () => { props .data!.refetch() .then(() => { - reject('Expected error value on first refetch.'); + reject("Expected error value on first refetch."); }) .catch(noop); break; @@ -463,7 +464,7 @@ describe('[queries] errors', () => { .data!.refetch() .then(noop) .catch(() => { - reject('Expected good data on second refetch.'); + reject("Expected good data on second refetch."); }); break; case 3: @@ -472,12 +473,10 @@ describe('[queries] errors', () => { case 4: expect(props.data!.loading).toBeFalsy(); expect(props.data!.error).toBeFalsy(); - expect(props.data!.allPeople).toEqual( - dataTwo.allPeople - ); + expect(props.data!.allPeople).toEqual(dataTwo.allPeople); break; default: - throw new Error('Unexpected fall through'); + throw new Error("Unexpected fall through"); } } catch (e) { reject(e); @@ -497,226 +496,139 @@ describe('[queries] errors', () => { ); waitFor(() => { - expect(count).toBe(5) + 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 + 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' }] } }; + `; + 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!') } - ); + 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; + const client = new ApolloClient({ + link, + cache: new Cache({ addTypename: false }), + }); + let count = 0; + const noop = () => null; - interface ContainerOwnProps { - hideContainer: Function; - } + interface ContainerOwnProps { + hideContainer: Function; + } - interface QueryChildProps { - data: DataValue; - 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 - }; + 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'); - } + )( + 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); } - } 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 + render() { + return null; } } - } - `; - 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> { + class Switcher extends React.Component { + constructor(props: any) { + super(props); + this.state = { + showContainer: true, + }; + } 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.'); + const { + state: { showContainer }, + } = this; + if (showContainer) { + return ( + this.setState({ showContainer: false })} + /> + ); } - 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) => } - - ); + render( + + + + ); - waitFor(() => expect(count).toBe(4)).then(resolve, reject); - }); + waitFor(() => { + expect(done).toBeTruthy(); + }).then(resolve, reject); + } + ); - describe('errorPolicy', () => { - itAsync('passes any GraphQL errors in props along with data', (resolve, reject) => { - let done = false; + itAsync( + "correctly sets loading state on remount after a network error", + (resolve, reject) => { const query: DocumentNode = gql` - query people { + query somethingelse { allPeople(first: 1) { people { name @@ -724,107 +636,206 @@ describe('[queries] errors', () => { } } `; - const link = mockSingleLink({ - request: { query }, - result: { - data: { - allPeople: { - people: null - } - }, - errors: [new Error('this is an error')] - } - }); + 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 }) + cache: new Cache({ addTypename: false }), }); - const ErrorContainer = graphql(query, { - options: { errorPolicy: 'all' } + let count = 0; + type ContainerOwnProps = { toggle: () => void }; + const Container = graphql(query, { + options: { notifyOnNetworkStatusChange: true }, })( - 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; - } + 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(done).toBe(true); - }).then(resolve, reject); - }); + 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")], + }, + }); - 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 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; } } - } - `; - const link = mockSingleLink({ - request: { query }, - result: { - data: { - allPeople: { - people: 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")], }, - errors: [new Error('this is an error')] - } - }); + }); - const client = new ApolloClient({ - link, - cache: new Cache({ addTypename: false }) - }); + 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; + 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) => } - - - ); + render( + + + {(props: any) => } + + + ); - waitFor(() => { - expect(done).toBe(true); - }).then(resolve, reject); - }); + 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 index 646a42aae94..45eaba87b96 100644 --- a/src/react/hoc/__tests__/queries/index.test.tsx +++ b/src/react/hoc/__tests__/queries/index.test.tsx @@ -1,18 +1,18 @@ -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', () => { +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; @@ -24,7 +24,7 @@ describe('queries', () => { }); // general api - it('binds a query to props', async() => { + it("binds a query to props", async () => { let done = false; const query: DocumentNode = gql` query people { @@ -37,11 +37,11 @@ describe('queries', () => { `; const link = mockSingleLink({ request: { query }, - result: { data: { allPeople: { people: [{ name: 'Luke Skywalker' }] } } } + result: { data: { allPeople: { people: [{ name: "Luke Skywalker" }] } } }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Data { @@ -50,13 +50,13 @@ describe('queries', () => { }; } - const ContainerWithData = graphql(query)( - ({ data }: DataProps) => { - expect(data).toBeTruthy(); - done = true; - return null; - } - ); + const ContainerWithData = graphql(query)(({ + data, + }: DataProps) => { + expect(data).toBeTruthy(); + done = true; + return null; + }); render( @@ -69,7 +69,7 @@ describe('queries', () => { }); }); - itAsync('includes the variables in the props', (resolve, reject) => { + itAsync("includes the variables in the props", (resolve, reject) => { const TIME_SCALE = 5000; let renderCount = 0; const query: DocumentNode = gql` @@ -85,11 +85,11 @@ describe('queries', () => { const variables = { first: 1 }; const link = mockSingleLink({ request: { query, variables }, - result: { data: { allPeople: { people: [{ name: 'Luke Skywalker' }] } } } + result: { data: { allPeople: { people: [{ name: "Luke Skywalker" }] } } }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); // Ensure variable types work correctly here @@ -106,14 +106,14 @@ describe('queries', () => { first: number; } - const ContainerWithData = graphql(query)( - ({ data }: ChildProps) => { - expect(data).toBeTruthy(); - expect(data!.variables).toEqual(variables); - renderCount += 1; - return null; - } - ); + const ContainerWithData = graphql(query)(({ + data, + }: ChildProps) => { + expect(data).toBeTruthy(); + expect(data!.variables).toEqual(variables); + renderCount += 1; + return null; + }); render( @@ -121,96 +121,104 @@ describe('queries', () => { ); - waitFor(() => { - expect(renderCount).toBe(2); - }, {timeout: TIME_SCALE}).then(resolve, reject); + 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 + 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' }] } } + 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" }] } }, + }, } - }, - { - request: { query, variables: { someId: 2 } }, - result: { data: { allPeople: { people: [{ name: 'Darth Vader' }] } } } - } - ); - const client = new ApolloClient({ - link, - cache: new Cache({ addTypename: false }) - }); + ); + const client = new ApolloClient({ + link, + cache: new Cache({ addTypename: false }), + }); - interface Data { - allPeople: { - people: { - name: string; + interface Data { + allPeople: { + people: { + name: string; + }; }; - }; - } + } - interface Variables { - someId: number; - } + interface Variables { + someId: number; + } - const options = { - options: {} - }; + 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})`); - } + 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; - }); + return null; + }); - const { rerender } = render( - - - - ); - rerender( - - - - ); + const { rerender } = render( + + + + ); + rerender( + + + + ); - waitFor(() => { - expect(count).toBe(3); - }).then(resolve, reject); - }); + waitFor(() => { + expect(count).toBe(3); + }).then(resolve, reject); + } + ); it("shouldn't warn about fragments", () => { const oldWarn = console.warn; @@ -218,20 +226,18 @@ describe('queries', () => { console.warn = (str: any) => warnings.push(str); try { - graphql( - gql` - query foo { - bar - } - ` - ); + graphql(gql` + query foo { + bar + } + `); expect(warnings.length).toEqual(0); } finally { console.warn = oldWarn; } }); - itAsync('executes a query', (resolve, reject) => { + itAsync("executes a query", (resolve, reject) => { let done = false; const query: DocumentNode = gql` query people { @@ -242,16 +248,16 @@ describe('queries', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; type Data = typeof data; const link = mockSingleLink({ request: { query }, - result: { data } + result: { data }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const Container = graphql<{}, Data>(query)( @@ -279,7 +285,7 @@ describe('queries', () => { }).then(resolve, reject); }); - itAsync('executes a query with two root fields', (resolve, reject) => { + itAsync("executes a query with two root fields", (resolve, reject) => { let done = false; const query: DocumentNode = gql` query people { @@ -296,18 +302,18 @@ describe('queries', () => { } `; const data = { - allPeople: { people: [{ name: 'Luke Skywalker' }] }, - otherPeople: { people: [{ name: 'Luke Skywalker' }] } + allPeople: { people: [{ name: "Luke Skywalker" }] }, + otherPeople: { people: [{ name: "Luke Skywalker" }] }, }; type Data = typeof data; const link = mockSingleLink({ request: { query }, - result: { data } + result: { data }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const Container = graphql<{}, Data>(query)( @@ -316,9 +322,7 @@ describe('queries', () => { const { props } = this; expect(props.data!.loading).toBeFalsy(); expect(props.data!.allPeople).toEqual(data.allPeople); - expect(props.data!.otherPeople).toEqual( - data.otherPeople - ); + expect(props.data!.otherPeople).toEqual(data.otherPeople); done = true; } render() { @@ -338,7 +342,7 @@ describe('queries', () => { }).then(resolve, reject); }); - itAsync('maps props as variables if they match', (resolve, reject) => { + itAsync("maps props as variables if they match", (resolve, reject) => { let done = false; const query: DocumentNode = gql` query people($first: Int) { @@ -349,7 +353,7 @@ describe('queries', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; type Data = typeof data; const variables = { first: 1 }; @@ -357,11 +361,11 @@ describe('queries', () => { const link = mockSingleLink({ request: { query, variables }, - result: { data } + result: { data }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const Container = graphql(query)( @@ -370,9 +374,7 @@ describe('queries', () => { const { props } = this; expect(props.data!.loading).toBeFalsy(); expect(props.data!.allPeople).toEqual(data.allPeople); - expect(props.data!.variables).toEqual( - this.props.data!.variables - ); + expect(props.data!.variables).toEqual(this.props.data!.variables); done = true; } render() { @@ -392,129 +394,135 @@ describe('queries', () => { }).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 + 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 + `; + 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, + }, }, - 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 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); + 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() { - return null; - } - } - ); - - render( - - - - ); + ); - waitFor(() => { - expect(done).toBe(true); - }).then(resolve, reject); - }); + render( + + + + ); - 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 + 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 data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; + type Data = typeof data; - const variables = { first: null }; - type Vars = typeof variables; + 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 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; + 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( - - - - ); + render( + + + + ); - waitFor(() => { - expect(done).toBe(true); - }).then(resolve, reject); - }); + waitFor(() => { + expect(done).toBe(true); + }).then(resolve, reject); + } + ); it("doesn't error on optional required props", () => { const query: DocumentNode = gql` @@ -526,7 +534,7 @@ describe('queries', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; type Data = typeof data; const variables = { first: 1 }; @@ -534,11 +542,11 @@ describe('queries', () => { const link = mockSingleLink({ request: { query, variables }, - result: { data } + result: { data }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const Container = graphql(query)(() => null); @@ -549,7 +557,7 @@ describe('queries', () => { ); - unmount() + unmount(); } catch (e) { errorCaught = e; } @@ -558,7 +566,7 @@ describe('queries', () => { }); // context - itAsync('allows context through updates', (resolve, reject) => { + itAsync("allows context through updates", (resolve, reject) => { const query: DocumentNode = gql` query people { allPeople(first: 1) { @@ -568,16 +576,16 @@ describe('queries', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; type Data = typeof data; const link = mockSingleLink({ request: { query }, - result: { data } + result: { data }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const Container = graphql<{}, Data>(query)( @@ -594,10 +602,13 @@ describe('queries', () => { } ); - class ContextContainer extends React.Component { + class ContextContainer extends React.Component< + React.PropsWithChildren, + { color: string } + > { constructor(props: {}) { super(props); - this.state = { color: 'purple' }; + this.state = { color: "purple" }; } getChildContext() { @@ -606,7 +617,7 @@ describe('queries', () => { componentDidMount() { setTimeout(() => { - this.setState({ color: 'green' }); + this.setState({ color: "green" }); }, 50); } @@ -617,7 +628,7 @@ describe('queries', () => { } (ContextContainer as any).childContextTypes = { - color: PropTypes.string + color: PropTypes.string, }; let count = 0; @@ -625,9 +636,9 @@ describe('queries', () => { class ChildContextContainer extends React.Component { render() { const { color } = this.context as any; - if (count === 0) expect(color).toBe('purple'); + if (count === 0) expect(color).toBe("purple"); if (count === 1) { - expect(color).toBe('green'); + expect(color).toBe("green"); done = true; } @@ -638,7 +649,7 @@ describe('queries', () => { } (ChildContextContainer as any).contextTypes = { - color: PropTypes.string + color: PropTypes.string, }; render( @@ -655,7 +666,7 @@ describe('queries', () => { }); // meta - it('stores the component name', () => { + it("stores the component name", () => { const query: DocumentNode = gql` query people { allPeople(first: 1) { @@ -665,7 +676,7 @@ describe('queries', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; type Data = typeof data; const Container = graphql<{}, Data>(query)( class Container extends React.Component> { @@ -675,7 +686,7 @@ describe('queries', () => { } ); - expect(Container.displayName).toEqual('Apollo(Container)'); + expect(Container.displayName).toEqual("Apollo(Container)"); }); it("uses a custom wrapped component name when 'alias' is specified", () => { @@ -689,7 +700,7 @@ describe('queries', () => { } `; @graphql(query, { - alias: 'withFoo' + alias: "withFoo", }) class Container extends React.Component { render(): React.ReactNode { @@ -699,10 +710,10 @@ describe('queries', () => { // ); // Not sure why I have to cast Container to any - expect((Container as any).displayName).toEqual('withFoo(Container)'); + expect((Container as any).displayName).toEqual("withFoo(Container)"); }); - itAsync('passes context to the link', (resolve, reject) => { + itAsync("passes context to the link", (resolve, reject) => { let done = false; const query: DocumentNode = gql` query people { @@ -721,13 +732,13 @@ describe('queries', () => { mockSingleLink({ request: { query }, result: { - data: { allPeople: { people: [{ name: 'Luke Skywalker' }] } } - } + data: { allPeople: { people: [{ name: "Luke Skywalker" }] } }, + }, }) ); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Data { @@ -737,7 +748,7 @@ describe('queries', () => { } const ContainerWithData = graphql(query, { - options: props => ({ context: { fromProps: props.context } }) + options: (props) => ({ context: { fromProps: props.context } }), })(() => null); render( @@ -751,21 +762,21 @@ describe('queries', () => { }).then(resolve, reject); }); - describe('Return partial data', () => { + describe("Return partial data", () => { const consoleWarn = console.warn; beforeAll(() => { - console.warn = () => null + console.warn = () => null; }); afterAll(() => { console.warn = consoleWarn; }); - it('should not return partial cache data when `returnPartialData` is false', () => { + it("should not return partial cache data when `returnPartialData` is false", () => { const cache = new Cache(); const client = new ApolloClient({ cache, - link: ApolloLink.empty() + link: ApolloLink.empty(), }); const fullQuery = gql` @@ -786,20 +797,20 @@ describe('queries', () => { data: { cars: [ { - __typename: 'Car', - make: 'Ford', - model: 'Mustang', - vin: 'PONY123', + __typename: "Car", + make: "Ford", + model: "Mustang", + vin: "PONY123", repairs: [ { - __typename: 'Repair', - date: '2019-05-08', - description: 'Could not get after it.' - } - ] - } - ] - } + __typename: "Repair", + date: "2019-05-08", + description: "Could not get after it.", + }, + ], + }, + ], + }, }); const partialQuery = gql` @@ -831,11 +842,11 @@ describe('queries', () => { render(); }); - it('should return partial cache data when `returnPartialData` is true', () => { + it("should return partial cache data when `returnPartialData` is true", () => { const cache = new Cache(); const client = new ApolloClient({ cache, - link: ApolloLink.empty() + link: ApolloLink.empty(), }); const fullQuery = gql` @@ -856,20 +867,20 @@ describe('queries', () => { data: { cars: [ { - __typename: 'Car', - make: 'Ford', - model: 'Mustang', - vin: 'PONY123', + __typename: "Car", + make: "Ford", + model: "Mustang", + vin: "PONY123", repairs: [ { - __typename: 'Repair', - date: '2019-05-08', - description: 'Could not get after it.' - } - ] - } - ] - } + __typename: "Repair", + date: "2019-05-08", + description: "Could not get after it.", + }, + ], + }, + ], + }, }); const partialQuery = gql` @@ -885,22 +896,22 @@ describe('queries', () => { const ComponentWithData = graphql(partialQuery, { options: { - returnPartialData: true - } + returnPartialData: true, + }, })( class Compnent extends React.Component { render() { if (!this.props.data.loading) { expect(this.props.data.cars).toEqual([ { - __typename: 'Car', + __typename: "Car", repairs: [ { - __typename: 'Repair', - date: '2019-05-08' - } - ] - } + __typename: "Repair", + date: "2019-05-08", + }, + ], + }, ]); } return null; diff --git a/src/react/hoc/__tests__/queries/lifecycle.test.tsx b/src/react/hoc/__tests__/queries/lifecycle.test.tsx index 2ecd58c5891..14dac2e15df 100644 --- a/src/react/hoc/__tests__/queries/lifecycle.test.tsx +++ b/src/react/hoc/__tests__/queries/lifecycle.test.tsx @@ -1,19 +1,19 @@ -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 } from '../../types'; - -describe('[queries] lifecycle', () => { +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 } from "../../types"; + +describe("[queries] lifecycle", () => { // lifecycle - it('reruns the query if it changes', async () => { + it("reruns the query if it changes", async () => { let count = 0; const query: DocumentNode = gql` query people($first: Int) { @@ -25,12 +25,12 @@ describe('[queries] lifecycle', () => { } `; - const data1 = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + 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 data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } }; const variables2 = { first: 2 }; const link = mockSingleLink( @@ -40,14 +40,14 @@ describe('[queries] lifecycle', () => { const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const Container = graphql(query, { - options: props => ({ + options: (props) => ({ variables: props, - fetchPolicy: count === 0 ? 'cache-and-network' : 'cache-first' - }) + fetchPolicy: count === 0 ? "cache-and-network" : "cache-first", + }), })( class extends React.Component> { componentDidUpdate(prevProps: ChildProps) { @@ -108,7 +108,7 @@ describe('[queries] lifecycle', () => { await waitFor(() => expect(count).toBe(3)); }); - it('rebuilds the queries on prop change when using `options`', async () => { + it("rebuilds the queries on prop change when using `options`", async () => { const query: DocumentNode = gql` query people { allPeople(first: 1) { @@ -118,16 +118,16 @@ describe('[queries] lifecycle', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; type Data = typeof data; const link = mockSingleLink({ request: { query }, - result: { data } + result: { data }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let firstRun = true; @@ -174,7 +174,7 @@ describe('[queries] lifecycle', () => { }); }); - it('reruns the query if just the variables change', async () => { + it("reruns the query if just the variables change", async () => { let count = 0; const query: DocumentNode = gql` query people($first: Int) { @@ -186,13 +186,13 @@ describe('[queries] lifecycle', () => { } `; - const data1 = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + 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 data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } }; const variables2 = { first: 2 }; const link = mockSingleLink( @@ -202,11 +202,11 @@ describe('[queries] lifecycle', () => { const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const Container = graphql(query, { - options: props => ({ variables: props }) + options: (props) => ({ variables: props }), })( class extends React.Component> { componentDidUpdate(prevProps: ChildProps) { @@ -268,7 +268,7 @@ describe('[queries] lifecycle', () => { await waitFor(() => expect(count).toBe(3)); }); - it('reruns the queries on prop change when using passed props', async () => { + it("reruns the queries on prop change when using passed props", async () => { let count = 0; const query: DocumentNode = gql` query people($first: Int) { @@ -280,13 +280,13 @@ describe('[queries] lifecycle', () => { } `; - const data1 = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + 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 data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } }; const variables2 = { first: 2 }; const link = mockSingleLink( @@ -296,7 +296,7 @@ describe('[queries] lifecycle', () => { const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const Container = graphql(query)( @@ -357,7 +357,7 @@ describe('[queries] lifecycle', () => { await waitFor(() => expect(count).toBe(3)); }); - it('stays subscribed to updates after irrelevant prop changes', async () => { + it("stays subscribed to updates after irrelevant prop changes", async () => { const query: DocumentNode = gql` query people($first: Int) { allPeople(first: $first) { @@ -369,17 +369,17 @@ describe('[queries] lifecycle', () => { `; const variables = { first: 1 }; type Vars = typeof variables; - const data1 = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; type Data = typeof data1; - const data2 = { allPeople: { people: [{ name: 'Leia Skywalker' }] } }; + 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 }) + cache: new Cache({ addTypename: false }), }); interface Props { @@ -389,7 +389,7 @@ describe('[queries] lifecycle', () => { let count = 0; const Container = graphql(query, { - options: { variables, notifyOnNetworkStatusChange: false } + options: { variables, notifyOnNetworkStatusChange: false }, })( class extends React.Component> { componentDidUpdate() { @@ -399,23 +399,17 @@ describe('[queries] lifecycle', () => { if (count === 1) { expect(props.foo).toEqual(42); expect(props.data!.loading).toEqual(false); - expect(props.data!.allPeople).toEqual( - data1.allPeople - ); + 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 - ); + expect(props.data!.allPeople).toEqual(data1.allPeople); 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 - ); + expect(props.data!.allPeople).toEqual(data2.allPeople); } } catch (e) { fail(e); @@ -448,7 +442,7 @@ describe('[queries] lifecycle', () => { await waitFor(() => expect(count).toBe(3)); }); - it('correctly rebuilds props on remount', async () => { + it("correctly rebuilds props on remount", async () => { const query: DocumentNode = gql` query pollingPeople { allPeople(first: 1) { @@ -458,7 +452,7 @@ describe('[queries] lifecycle', () => { } } `; - const data = { allPeople: { people: [{ name: 'Darth Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Darth Skywalker" }] } }; type Data = typeof data; const link = mockSingleLink({ request: { query }, @@ -466,14 +460,14 @@ describe('[queries] lifecycle', () => { newData: () => ({ data: { allPeople: { - people: [{ name: `Darth Skywalker - ${Math.random()}` }] - } - } - }) + people: [{ name: `Darth Skywalker - ${Math.random()}` }], + }, + }, + }), }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let app: React.ReactElement, count = 0; @@ -481,7 +475,7 @@ describe('[queries] lifecycle', () => { let done = false; let rerender: any; const Container = graphql<{}, Data>(query, { - options: { pollInterval: 10, notifyOnNetworkStatusChange: false } + options: { pollInterval: 10, notifyOnNetworkStatusChange: false }, })( class extends React.Component> { componentDidUpdate() { @@ -510,11 +504,11 @@ describe('[queries] lifecycle', () => { rerender = render(app).rerender; await waitFor(() => { - expect(done).toBeTruthy() + expect(done).toBeTruthy(); }); }); - it('will re-execute a query when the client changes', async () => { + it("will re-execute a query when the client changes", async () => { const query: DocumentNode = gql` { a @@ -525,38 +519,38 @@ describe('[queries] lifecycle', () => { const link1 = mockSingleLink( { request: { query }, - result: { data: { a: 1, b: 2, c: 3 } } + result: { data: { a: 1, b: 2, c: 3 } }, }, { request: { query }, - result: { data: { a: 1, b: 2, c: 3 } } + result: { data: { a: 1, b: 2, c: 3 } }, } ); const link2 = mockSingleLink( { request: { query }, - result: { data: { a: 4, b: 5, c: 6 } } + result: { data: { a: 4, b: 5, c: 6 } }, }, { request: { query }, - result: { data: { a: 4, b: 5, c: 6 } } + result: { data: { a: 4, b: 5, c: 6 } }, } ); const link3 = mockSingleLink({ request: { query }, - result: { data: { a: 7, b: 8, c: 9 } } + result: { data: { a: 7, b: 8, c: 9 } }, }); const client1 = new ApolloClient({ link: link1, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const client2 = new ApolloClient({ link: link2, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const client3 = new ApolloClient({ link: link3, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Data { @@ -570,7 +564,7 @@ describe('[queries] lifecycle', () => { let testFailures: any[] = []; const Query = graphql<{}, Data>(query, { - options: { notifyOnNetworkStatusChange: true } + options: { notifyOnNetworkStatusChange: true }, })( class extends React.Component> { componentDidMount() { @@ -594,7 +588,7 @@ describe('[queries] lifecycle', () => { loading: false, a: 1, b: 2, - c: 3 + c: 3, }); refetchQuery!(); break; @@ -603,7 +597,7 @@ describe('[queries] lifecycle', () => { expect({ a, b, c }).toEqual({ a: 1, b: 2, - c: 3 + c: 3, }); break; case 4: @@ -611,7 +605,7 @@ describe('[queries] lifecycle', () => { loading: false, a: 1, b: 2, - c: 3 + c: 3, }); setTimeout(() => { switchClient!(client2); @@ -630,7 +624,7 @@ describe('[queries] lifecycle', () => { loading: false, a: 4, b: 5, - c: 6 + c: 6, }); refetchQuery!(); break; @@ -639,7 +633,7 @@ describe('[queries] lifecycle', () => { loading: true, a: 4, b: 5, - c: 6 + c: 6, }); break; case 8: @@ -647,7 +641,7 @@ describe('[queries] lifecycle', () => { loading: false, a: 4, b: 5, - c: 6 + c: 6, }); setTimeout(() => { switchClient!(client3); @@ -666,7 +660,7 @@ describe('[queries] lifecycle', () => { loading: false, a: 7, b: 8, - c: 9 + c: 9, }); setTimeout(() => { switchClient!(client1); @@ -677,7 +671,7 @@ describe('[queries] lifecycle', () => { loading: false, a: 1, b: 2, - c: 3 + c: 3, }); setTimeout(() => { switchClient!(client3); @@ -705,11 +699,11 @@ describe('[queries] lifecycle', () => { class ClientSwitcher extends React.Component { state = { - client: client1 + client: client1, }; componentDidMount() { - switchClient = newClient => { + switchClient = (newClient) => { this.setState({ client: newClient }); }; } @@ -729,11 +723,11 @@ describe('[queries] lifecycle', () => { if (testFailures.length > 0) { throw testFailures[0]; } - expect(count).toBe(12) + expect(count).toBe(12); }); }); - it('handles synchronous racecondition with prefilled data from the server', async () => { + it("handles synchronous racecondition with prefilled data from the server", async () => { const query: DocumentNode = gql` query GetUser($first: Int) { user(first: $first) { @@ -743,29 +737,29 @@ describe('[queries] lifecycle', () => { `; const variables = { first: 1 }; type Vars = typeof variables; - const data2 = { user: { name: 'Luke Skywalker' } }; + const data2 = { user: { name: "Luke Skywalker" } }; type Data = typeof data2; const link = mockSingleLink({ request: { query, variables }, result: { data: data2 }, - delay: 10 + delay: 10, }); const initialState = { apollo: { data: { ROOT_QUERY: { - 'user({"first":1})': null - } - } - } + '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) + cache: new Cache({ addTypename: false }).restore(initialState), }); let count = 0; @@ -773,8 +767,8 @@ describe('[queries] lifecycle', () => { const Container = graphql(query)( class extends React.Component> { componentDidMount() { - this.props.data!.refetch().then(result => { - expect(result.data!.user.name).toBe('Luke Skywalker'); + this.props.data!.refetch().then((result) => { + expect(result.data!.user.name).toBe("Luke Skywalker"); done = true; }); } @@ -782,9 +776,9 @@ describe('[queries] lifecycle', () => { render() { count++; const user = this.props.data!.user; - const name = user ? user.name : ''; + const name = user ? user.name : ""; if (count === 2) { - expect(name).toBe('Luke Skywalker'); + expect(name).toBe("Luke Skywalker"); } return null; } @@ -800,7 +794,7 @@ describe('[queries] lifecycle', () => { await waitFor(() => expect(done).toBeTruthy()); }); - it('handles asynchronous racecondition with prefilled data from the server', async () => { + it("handles asynchronous racecondition with prefilled data from the server", async () => { const query: DocumentNode = gql` query Q { books { @@ -813,42 +807,42 @@ describe('[queries] lifecycle', () => { const ssrResult = { books: [ { - name: 'ssrfirst', - __typename: 'Book' - } - ] + name: "ssrfirst", + __typename: "Book", + }, + ], }; const result = { books: [ { - name: 'first', - __typename: 'Book' - } - ] + name: "first", + __typename: "Book", + }, + ], }; const ssrLink = mockSingleLink({ request: { query } as any, - result: { data: ssrResult } + result: { data: ssrResult }, }); const link = mockSingleLink({ request: { query } as any, - result: { data: result } + result: { data: result }, }); const ssrClient = new ApolloClient({ cache: new Cache(), - link: ssrLink + link: ssrLink, }); await ssrClient.query({ query, - variables: {} + variables: {}, }); const client = new ApolloClient({ cache: new Cache().restore(ssrClient.extract()), // --- this is the "SSR" bit - link + link, }); //try to render the app / call refetch / etc @@ -861,7 +855,7 @@ describe('[queries] lifecycle', () => { {({ loading, data, refetch }: any) => { if (!loading) { if (!refetched) { - expect(data.books[0].name).toEqual('ssrfirst'); + 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 @@ -871,13 +865,13 @@ describe('[queries] lifecycle', () => { //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'); + expect(refetchResult.data.books[0].name).toEqual("first"); done = true; }); }); refetched = true; } else { - expect(data.books[0].name).toEqual('first'); + expect(data.books[0].name).toEqual("first"); } } return

stub

; diff --git a/src/react/hoc/__tests__/queries/loading.test.tsx b/src/react/hoc/__tests__/queries/loading.test.tsx index 819ab63390f..8453b27ab44 100644 --- a/src/react/hoc/__tests__/queries/loading.test.tsx +++ b/src/react/hoc/__tests__/queries/loading.test.tsx @@ -1,62 +1,71 @@ -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 } from '../../types'; - -describe('[queries] loading', () => { +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 } from "../../types"; + +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 + 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 }) - }); + `; + 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; + 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( - - - - ); + render( + + + + ); - waitFor(() => expect(done).toBeTruthy()).then(resolve, reject); - }); + waitFor(() => expect(done).toBeTruthy()).then(resolve, reject); + } + ); - it('should set the initial networkStatus to 1 (loading)', () => { + it("should set the initial networkStatus to 1 (loading)", () => { const query: DocumentNode = gql` query people { allPeople(first: 1) { @@ -66,14 +75,14 @@ describe('[queries] loading', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; const link = mockSingleLink({ request: { query }, - result: { data } + result: { data }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); @graphql(query, { options: { notifyOnNetworkStatusChange: true } }) @@ -95,241 +104,260 @@ describe('[queries] loading', () => {
); - unmount() + 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 + 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 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; - } + 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() { + return null; + } } - } - ); + ); - render( - - - - ); + render( + + + + ); - waitFor(() => { - expect(done).toBe(true); - }).then(resolve, reject); - }); + 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 + 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 data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; + const variables1 = { first: 1 }; - const data2 = { allPeople: { people: [{ name: 'Leia Skywalker' }] } }; - const variables2 = { first: 2 }; + const data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } }; + const variables2 = { first: 2 }; - type Data = typeof data1; - type Vars = typeof variables1; + 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 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 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; + 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); } - } catch (err) { - reject(err); + } + render() { + return null; } } - render() { - return null; - } - } - ); + ); - class ChangingProps extends React.Component { - state = { first: 1 }; + class ChangingProps extends React.Component { + state = { first: 1 }; - componentDidMount() { - setTimeout(() => { - this.setState({ first: 2 }); - }, 50); - } + componentDidMount() { + setTimeout(() => { + this.setState({ first: 2 }); + }, 50); + } - render() { - return ; + render() { + return ; + } } - } - render( - - - - ); + render( + + + + ); - waitFor(() => expect(done).toBe(true)).then(resolve, reject); - }); + 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 + 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' }] } }; + `; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; + const data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } }; - type Data = typeof data; + 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 }) - }); + 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(() => { - 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')); + 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(() => { + 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() { + return null; + } } - } - ); + ); - render( - - - - ); + render( + + + + ); - waitFor(() => { - expect(count).toBe(3) - }).then(resolve, reject); - }); + waitFor(() => { + expect(count).toBe(3); + }).then(resolve, reject); + } + ); - it('correctly sets loading state on remounted network-only query', async () => { + it("correctly sets loading state on remounted network-only query", async () => { const query: DocumentNode = gql` query pollingPeople { allPeople(first: 1) { @@ -339,7 +367,7 @@ describe('[queries] loading', () => { } } `; - const data = { allPeople: { people: [{ name: 'Darth Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Darth Skywalker" }] } }; type Data = typeof data; const link = mockSingleLink({ @@ -348,16 +376,16 @@ describe('[queries] loading', () => { newData: () => ({ data: { allPeople: { - people: [{ name: `Darth Skywalker - ${Math.random()}` }] - } - } - }) + people: [{ name: `Darth Skywalker - ${Math.random()}` }], + }, + }, + }), }); const client = new ApolloClient({ link, cache: new Cache({ addTypename: false }), - queryDeduplication: false + queryDeduplication: false, }); let count = 0; @@ -365,7 +393,7 @@ describe('[queries] loading', () => { const usedFetchPolicies: WatchQueryFetchPolicy[] = []; const Container = graphql<{}, Data>(query, { options: { - fetchPolicy: 'network-only', + fetchPolicy: "network-only", nextFetchPolicy(currentFetchPolicy, info) { if (info.reason === "variables-changed") { return info.initialFetchPolicy; @@ -375,7 +403,7 @@ describe('[queries] loading', () => { return "cache-first"; } return currentFetchPolicy; - } + }, }, })( class extends React.Component> { @@ -415,300 +443,319 @@ describe('[queries] loading', () => { render(App); - await waitFor(() => { - expect(usedFetchPolicies).toEqual([ - "network-only", - "network-only", - "cache-first", - ]); - }, { interval: 1 }); - await waitFor(() => { + await waitFor( + () => { + expect(usedFetchPolicies).toEqual([ + "network-only", + "network-only", + "cache-first", + ]); + }, + { interval: 1 } + ); + await waitFor( + () => { expect(count).toBe(5); - }, { interval: 1 }); + }, + { interval: 1 } + ); }); - 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 + 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 }; - interface Data { - allPeople: { - people: { name: string }[]; - }; - } - const data = { allPeople: null }; - const variables = { first: 1 }; - const variables2 = { first: 2 }; + type Vars = typeof variables; - 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[] = []; - const link = mockSingleLink( - { request: { query, variables }, result: { data }, delay: 10 }, - { - request: { query, variables: variables2 }, - result: { data }, - delay: 10 + interface Props { + first: number; } - ); - 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); - } + 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(); + if (count === 2) { + // remounted data after fetch + expect(this.props.data!.loading).toBeFalsy(); + } + count++; + } catch (e) { + testFailures.push(e); } - count++; - } catch (e) { - testFailures.push(e); } - } - render() { - try { - if (count === 1) { - expect(this.props.data!.loading).toBeTruthy(); // on remount - count++; + render() { + try { + if (count === 1) { + expect(this.props.data!.loading).toBeTruthy(); // on remount + count++; + } + } catch (e) { + testFailures.push(e); } - } catch (e) { - testFailures.push(e); - } - return null; + 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); - }); + 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 + 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' }; + 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; + 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 }) - }); + 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; + 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 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( - - - - ); + const { unmount } = render( + + + + ); - waitFor(() => expect(count).toBe(4)).then(resolve, reject); - }); + 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 + 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 }[]; + }; } - `; - interface Data { - allPeople: { - people: { name: string }[]; - }; - } - const data = { allPeople: null }; - const variables = { first: 1 }; - const variables2 = { first: 2 }; + 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 } + 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 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); - const connect = ( - component: React.ComponentType>> - ): React.ComponentType>> => { - return class extends React.Component<{}, { first: number }> { - constructor(props: {}) { - super(props); - - this.state = { - first: 1 - }; - this.setFirst = this.setFirst.bind(this); - } + this.state = { + first: 1, + }; + this.setFirst = this.setFirst.bind(this); + } - setFirst(first: number) { - this.setState({ first }); - } + setFirst(first: number) { + this.setState({ first }); + } - render() { - return React.createElement(component, { - first: this.state.first, - setFirst: this.setFirst - }); - } + 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; + 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); } - } catch (err) { - reject(err); - } - count++; + count++; - return null; + return null; + } } - } - ) - ); + ) + ); - render( - - - - ); + render( + + + + ); - waitFor(() => expect(count).toBe(4)).then(resolve, reject); - }); + waitFor(() => expect(count).toBe(4)).then(resolve, reject); + } + ); itAsync( - 'correctly sets loading state on component with changed variables, ' + - 'unchanged result, and network-only', + "correctly sets loading state on component with changed variables, " + + "unchanged result, and network-only", (resolve, reject) => { const query: DocumentNode = gql` query remount($first: Int) { @@ -725,7 +772,7 @@ describe('[queries] loading', () => { }; } - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; const variables = { first: 1 }; const variables2 = { first: 2 }; @@ -735,12 +782,12 @@ describe('[queries] loading', () => { { request: { query, variables: variables2 }, result: { data }, - delay: 10 + delay: 10, } ); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let count = 0; @@ -749,14 +796,18 @@ describe('[queries] loading', () => { } const connect = ( - component: React.ComponentType>> - ): React.ComponentType>> => { + component: React.ComponentType< + React.PropsWithChildren> + > + ): React.ComponentType< + React.PropsWithChildren> + > => { return class extends React.Component<{}, { first: number }> { constructor(props: {}) { super(props); this.state = { - first: 1 + first: 1, }; this.setFirst = this.setFirst.bind(this); } @@ -768,7 +819,7 @@ describe('[queries] loading', () => { render() { return React.createElement(component, { first: this.state.first, - setFirst: this.setFirst + setFirst: this.setFirst, }); } }; @@ -778,8 +829,8 @@ describe('[queries] loading', () => { graphql(query, { options: ({ first }) => ({ variables: { first }, - fetchPolicy: 'network-only' - }) + fetchPolicy: "network-only", + }), })( class extends React.Component> { render() { diff --git a/src/react/hoc/__tests__/queries/observableQuery.test.tsx b/src/react/hoc/__tests__/queries/observableQuery.test.tsx index 71ad09a5b33..dd05663aac2 100644 --- a/src/react/hoc/__tests__/queries/observableQuery.test.tsx +++ b/src/react/hoc/__tests__/queries/observableQuery.test.tsx @@ -1,19 +1,19 @@ -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', () => { +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 () => { + it("will recycle `ObservableQuery`s when re-rendering the entire tree", async () => { const query: DocumentNode = gql` query people { allPeople(first: 1) { @@ -23,7 +23,7 @@ describe('[queries] observableQuery', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; type Data = typeof data; const link = mockSingleLink( @@ -32,7 +32,7 @@ describe('[queries] observableQuery', () => { ); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let count = 0; @@ -41,32 +41,30 @@ describe('[queries] observableQuery', () => { const keys = Array.from( ((client as any).queryManager as any).queries.keys() ); - await waitFor(() => expect(keys).toEqual(['1']), { interval: 1 }); + 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 }); + await waitFor(() => expect(keys).toEqual(["1"]), { interval: 1 }); }; let done = false; const Container = graphql<{}, Data>(query, { - options: { fetchPolicy: 'cache-and-network' } + 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 - ); + expect(this.props.data!.allPeople).toEqual(data.allPeople); // ensure first assertion and umount tree await assert1(); - userEvent.click(screen.getByText('Break things')); + userEvent.click(screen.getByText("Break things")); // ensure cleanup await assert2(); @@ -87,9 +85,7 @@ describe('[queries] observableQuery', () => { // be present; if (count === 3) { expect(this.props.data!.loading).toBeFalsy(); - expect(this.props.data!.allPeople).toEqual( - data.allPeople - ); + expect(this.props.data!.allPeople).toEqual(data.allPeople); } count++; return null; @@ -109,7 +105,7 @@ describe('[queries] observableQuery', () => { class AppWrapper extends React.Component<{}, { renderRedirect: boolean }> { state = { - renderRedirect: false + renderRedirect: false, }; goToRedirect = () => { @@ -147,129 +143,131 @@ describe('[queries] observableQuery', () => { }); }); - 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) { + 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' }] }] - } - }; + `; + 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; + 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 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); + } + } - 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); } - 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; } - - 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); - }); + class Remounter extends React.Component< + { render: typeof Container }, + { showChildren: boolean; variables: Vars } + > { + state = { + showChildren: true, + variables: variables1, }; - } - render() { - if (!this.state.showChildren) return null; - const Thing = this.props.render; - return ; + 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( - - - - ); + // 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(); + // 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 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); - }); + // 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 () => { + it("not overly rerender", async () => { const query: DocumentNode = gql` query people($first: Int!) { allPeople(first: $first) { @@ -286,20 +284,20 @@ describe('[queries] observableQuery', () => { const variables = { first: 1 }; const data = { allPeople: { - people: [{ name: 'Luke Skywalker', friends: [{ name: 'r2d2' }] }] - } + people: [{ name: "Luke Skywalker", friends: [{ name: "r2d2" }] }], + }, }; type Data = typeof data; type Vars = typeof variables; const link = mockSingleLink({ request: { query, variables }, - result: { data } + result: { data }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let remount: any; @@ -335,7 +333,7 @@ describe('[queries] observableQuery', () => { > { state = { showChildren: true, - variables + variables, }; componentDidMount() { @@ -382,93 +380,96 @@ describe('[queries] observableQuery', () => { }); }); - 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) { + 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 variables = { first: 1 }; + const dataOne = { + allPeople: { + people: [{ name: "Luke Skywalker", friends: [{ name: "r2d2" }] }], + }, + }; + const dataTwo = { + allPeople: { + people: [ + { name: "Luke Skywalker", friends: [{ name: "Leia Skywalker" }] }, + ], + }, + }; - const client = new ApolloClient({ - link, - cache: new Cache({ addTypename: false }) - }); + type Data = typeof dataOne; + type Vars = typeof variables; + + const link = mockSingleLink( + { + request: { query, variables }, + result: { data: dataOne }, + }, + { + request: { query, variables }, + result: { data: dataTwo }, + } + ); - 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'); + 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); } - } catch (e) { - reject(e); - } - return null; + return null; + } } - } - ); + ); - // the initial mount fires off the query - // the same as episode id = 1 - render( - - - - ); + // the initial mount fires off the query + // the same as episode id = 1 + render( + + + + ); - return waitFor(() => expect(count).toBe(3)).then(resolve, reject); - }); + 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 index 37786c5d300..70441733341 100644 --- a/src/react/hoc/__tests__/queries/polling.test.tsx +++ b/src/react/hoc/__tests__/queries/polling.test.tsx @@ -1,17 +1,17 @@ -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', () => { +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(() => { @@ -25,7 +25,7 @@ describe('[queries] polling', () => { }); // polling - itAsync('allows a polling query to be created', (resolve, reject) => { + itAsync("allows a polling query to be created", (resolve, reject) => { const POLL_INTERVAL = 5; const query: DocumentNode = gql` query people { @@ -36,14 +36,14 @@ describe('[queries] polling', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; - const data2 = { allPeople: { people: [{ name: 'Leia Skywalker' }] } }; + 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 cache = new Cache({ addTypename: false }); const client = new ApolloClient({ link, cache, @@ -54,12 +54,12 @@ describe('[queries] polling', () => { options: () => ({ pollInterval: POLL_INTERVAL, notifyOnNetworkStatusChange: false, - }) + }), })(({ data }) => { count++; if (count === 4) { data!.stopPolling(); - expect(cache.readQuery({query})).toBeTruthy(); + expect(cache.readQuery({ query })).toBeTruthy(); resolve(); } return null; @@ -74,55 +74,58 @@ describe('[queries] polling', () => { 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 + 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; - }); + `; + 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( - - - - ); + render( + + + + ); - waitFor(() => expect(count).toBe(4)).then(resolve, reject); - }); + waitFor(() => expect(count).toBe(4)).then(resolve, reject); + } + ); const allPeopleQuery: DocumentNode = gql` query people { @@ -134,25 +137,26 @@ describe('[queries] polling', () => { } `; - const lukeLink = new ApolloLink(operation => new Observable(observer => { - expect(operation.query).toBe(allPeopleQuery); - observer.next({ - data: { - allPeople: { - people: [ - { name: "Luke Skywalker" }, - ], - }, - }, - }); - observer.complete(); - })); + 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) => { + 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 }) + cache: new Cache({ addTypename: false }), }); const Container = graphql(allPeopleQuery)( @@ -184,39 +188,44 @@ describe('[queries] polling', () => { }).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); + 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() { - return null; - } - } - ); + ); - render( - - - - ); + render( + + + + ); - waitFor(() => { - expect(done).toBe(true); - }).then(resolve, reject); - }); + waitFor(() => { + expect(done).toBe(true); + }).then(resolve, reject); + } + ); }); diff --git a/src/react/hoc/__tests__/queries/recomposeWithState.ts b/src/react/hoc/__tests__/queries/recomposeWithState.ts index 578148db490..a1bde0a6c18 100644 --- a/src/react/hoc/__tests__/queries/recomposeWithState.ts +++ b/src/react/hoc/__tests__/queries/recomposeWithState.ts @@ -34,7 +34,10 @@ export const withState = (stateName: string, stateUpdaterName: string, initialState: unknown) => (BaseComponent: React.ComponentClass) => { const factory = createFactory(BaseComponent); - class WithState extends Component, { stateValue: unknown }> { + class WithState extends Component< + Record, + { stateValue: unknown } + > { state = { stateValue: typeof initialState === "function" diff --git a/src/react/hoc/__tests__/queries/reducer.test.tsx b/src/react/hoc/__tests__/queries/reducer.test.tsx index ae75bcd7d80..93407151864 100644 --- a/src/react/hoc/__tests__/queries/reducer.test.tsx +++ b/src/react/hoc/__tests__/queries/reducer.test.tsx @@ -1,18 +1,18 @@ -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', () => { +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) => { + itAsync("allows custom mapping of a result to props", (resolve, reject) => { const query: DocumentNode = gql` query thing { getThing { @@ -23,11 +23,11 @@ describe('[queries] reducer', () => { const result = { getThing: { thing: true } }; const link = mockSingleLink({ request: { query }, - result: { data: result } + result: { data: result }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); type Data = typeof result; @@ -36,7 +36,7 @@ describe('[queries] reducer', () => { let count = 0; const ContainerWithData = graphql<{}, Data, {}, ChildProps>(query, { - props: ({ data }) => ({ ...data! }) + props: ({ data }) => ({ ...data! }), })(({ getThing, loading }) => { count++; if (count === 1) expect(loading).toBe(true); @@ -55,60 +55,63 @@ describe('[queries] reducer', () => { 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 + 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 }; } - `; - 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(); + interface Props { + sample: number; } - count += 1; - return null; - }); - render( - - - - ); + 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; + }); - waitFor(() => { - expect(count).toBe(2); - }).then(resolve, reject); - }); + render( + + + + ); + + waitFor(() => { + expect(count).toBe(2); + }).then(resolve, reject); + } + ); - itAsync('allows custom mapping of a result to props 2', (resolve, reject) => { + itAsync("allows custom mapping of a result to props 2", (resolve, reject) => { let done = false; const query: DocumentNode = gql` query thing { @@ -120,11 +123,11 @@ describe('[queries] reducer', () => { const expectedData = { getThing: { thing: true } }; const link = mockSingleLink({ request: { query }, - result: { data: expectedData } + result: { data: expectedData }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Data { @@ -136,7 +139,7 @@ describe('[queries] reducer', () => { } const withData = graphql<{}, Data, {}, FinalProps>(query, { - props: ({ data }) => ({ thingy: data!.getThing! }) + props: ({ data }) => ({ thingy: data!.getThing! }), }); class Container extends React.Component { @@ -158,85 +161,89 @@ describe('[queries] reducer', () => { ); waitFor(() => { - expect(done).toBe(true) + 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 + itAsync( + "passes the prior props to the result-props mapper", + (resolve, reject) => { + const query: DocumentNode = gql` + query thing { + getThing { + thing + } + other } - 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!; + `; + 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 }, } - - return { wrapper, refetch }; + ); + const client = new ApolloClient({ + link, + cache: new Cache({ addTypename: false }), + }); + + type Data = typeof expectedData; + interface FinalProps { + wrapper: { thingy: { thing: boolean } }; + refetch: () => any; } - }); - 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(); + 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; } } - render() { - return null; - } - } - const ContainerWithData = withData(Container); + const ContainerWithData = withData(Container); - render( - - - - ); + render( + + + + ); - waitFor(() => expect(done).toBeTruthy()).then(resolve, reject); - }); + 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 index 4a25a770c73..ee0892df472 100644 --- a/src/react/hoc/__tests__/queries/skip.test.tsx +++ b/src/react/hoc/__tests__/queries/skip.test.tsx @@ -1,744 +1,772 @@ -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 +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 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; } } - } - `; + ); - 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; - } + render( + + + + ); - let done = false; - const Container = graphql(query, { skip: true })( - class extends React.Component> { - componentDidUpdate() { + let done = false; + setTimeout(() => { + if (!queryExecuted) { done = true; + return; } - render() { - return null; - } - } - ); + reject(new Error("query ran even though skip present")); + }, 25); - class Parent extends React.Component<{}, { foo: number }> { - state = { foo: 42 }; - - componentDidMount() { - this.setState({ foo: 43 }); - } - render() { - return ; - } + waitFor(() => expect(done).toBeTruthy()).then(resolve, reject); } - - 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 + ); + + 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 data = { - allPeople: { people: { id: 1 } } - }; + 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 }), + }); - type Data = typeof data; - - const variables = { id: 1 }; - type Vars = typeof variables; - - const link = mockSingleLink({ - request: { query, variables }, - result: { data } - }); + interface Props { + foo: number; + } - const client = new ApolloClient({ - link, - cache: new Cache({ addTypename: false }) - }); + let done = false; + const Container = graphql(query, { skip: true })( + class extends React.Component> { + componentDidUpdate() { + done = true; + } + render() { + return null; + } + } + ); - let count = 0; - let renderCount = 0; + class Parent extends React.Component<{}, { foo: number }> { + state = { foo: 42 }; - 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); - } + componentDidMount() { + this.setState({ foo: 43 }); } render() { - renderCount++; - return null; + return ; } } - ); - class Parent extends React.Component< - {}, - { person: { id: number } | null } - > { - state = { person: null }; + render( + + + + ); - componentDidMount() { - this.setState({ person: { id: 1 } }); - } - render() { - return ; - } + waitFor(() => expect(done).toBeTruthy()).then(resolve, reject); } - - 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 + ); + + 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: [{ 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; - } + `; + + 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; + } + } + ); - interface FinalProps { - pollInterval: number; - data?: {}; - } + class Parent extends React.Component< + {}, + { person: { id: number } | null } + > { + state = { person: null }; - 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; + componentDidMount() { + this.setState({ person: { id: 1 } }); } render() { - expect(this.props.data).toBeFalsy(); - return null; + return ; } } - ); - - 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); - }); + render( + + + + ); - 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 + 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 link = mockSingleLink({ - request: { query }, - result: {} - }); - - const client = new ApolloClient({ - link, - cache: new Cache({ addTypename: false }) - }); - - let queryWasSkipped = true; + `; + 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; + } + } + ); - interface Props { - foo: string; - } + render( + + + + ); - 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(); + let done = false; + setTimeout(() => { + if (!queryExecuted) { done = true; + return; } - render() { - return null; + if (optionsCalled) { + reject(new Error("options ran even though skip present")); + return; } - } - ); + reject(new Error("query ran even though skip present")); + }, 25); - class Parent extends React.Component<{}, { foo: string }> { - state = { foo: 'bar' }; - componentDidMount() { - this.setState({ foo: 'baz' }); - } - render() { - return ; - } + waitFor(() => expect(done).toBeTruthy()).then(resolve, reject); } - - 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 + ); + + 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 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; - } - } - ); + const link = mockSingleLink({ + request: { query }, + result: {}, + }); - render( - - - - ); + const client = new ApolloClient({ + link, + cache: new Cache({ addTypename: false }), + }); - let done = false; - setTimeout(() => { - if (!queryExecuted) { - done = true; - return; - } - reject(new Error('query ran even though skip present')); - }, 25); + let queryWasSkipped = true; - waitFor(() => expect(done).toBeTruthy()).then(resolve, reject); - }); + interface Props { + foo: string; + } - // 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 + 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; } } - } - `; - 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); - } - } + class Parent extends React.Component<{}, { foo: string }> { + state = { foo: "bar" }; + componentDidMount() { + this.setState({ foo: "baz" }); } render() { - return null; + return ; } } - ); - - class Parent extends React.Component { - state = { skip: false }; - render() { - return ( - this.setState({ skip })} - /> - ); - } - } - - render( - - - - ); - waitFor(() => expect(hasSkipped).toBeTruthy()).then(resolve, reject); - }); + render( + + + + ); - 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 + 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 dataOne = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; - const dataTwo = { allPeople: { people: [{ name: 'Leia Skywalker' }] } }; - - type Data = typeof dataOne; - type Vars = { first: number }; + `; + 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; + } + } + ); - 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 }) - }); + render( + + + + ); - let hasSkipped = false; + let done = false; + setTimeout(() => { + if (!queryExecuted) { + done = true; + return; + } + reject(new Error("query ran even though skip present")); + }, 25); - interface Props { - skip: boolean; - first: number; - setState: ( - state: Pick<{ skip: boolean; first: number }, K> - ) => void; + waitFor(() => expect(done).toBeTruthy()).then(resolve, reject); } + ); - 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; - } + // 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 { - expect(this.props.data!.allPeople).toEqual( - dataOne.allPeople - ); - prevProps.setState({ skip: true }); + if (!hasSkipped) { + prevProps.setSkip(true); + } } } + render() { + return null; + } } + ); + + class Parent extends React.Component { + state = { skip: false }; render() { - return null; + return ( + this.setState({ skip })} + /> + ); } } - ); - - 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); - }); + render( + + + + ); - 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 + 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 data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; - const nextData = { allPeople: { people: [{ name: 'Anakin Skywalker' }] } }; - const finalData = { allPeople: { people: [{ name: 'Darth Vader' }] } }; + `; + const dataOne = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; + const dataTwo = { allPeople: { people: [{ name: "Leia Skywalker" }] } }; - let ranQuery = 0; + type Data = typeof dataOne; + type Vars = { first: number }; - const link = new ApolloLink((o, f) => { - ranQuery++; - return f ? f(o) : null; - }).concat( - mockSingleLink( + const link = mockSingleLink( { - request: { query }, - result: { data }, + request: { query, variables: { first: 1 } }, + result: { data: dataOne }, }, { - request: { query }, - result: { data: nextData }, + request: { query, variables: { first: 2 } }, + result: { data: dataTwo }, }, { - request: { query }, - result: { data: finalData }, - }, - ) - ); - - const client = new ApolloClient({ - link, - cache: new Cache({ addTypename: false }), - queryDeduplication: false - }); + 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; + } + } + ); - let count = 0; - const Container = graphql(query, { - options: { - fetchPolicy: 'network-only', - nextFetchPolicy: 'cache-first', - notifyOnNetworkStatusChange: true - }, - skip: ({ skip }) => skip - })( - class extends React.Component { + class Parent extends React.Component< + {}, + { skip: boolean; first: number } + > { + state = { skip: false, first: 1 }; 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})`); + 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 } - } catch (err) { - reject(err); } + } + `; + + 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; + return null; + } } - }, - ); + ); - class Parent extends React.Component<{}, { skip: boolean }> { - state = { skip: false }; - render() { - return ( - this.setState({ skip })} - /> - ); + class Parent extends React.Component<{}, { skip: boolean }> { + state = { skip: false }; + render() { + return ( + this.setState({ skip })} + /> + ); + } } - } - render( - - - - ); + render( + + + + ); - waitFor(() => { - expect(count).toEqual(7) - }).then(resolve, reject); - })); + 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 () => { + it.skip("removes the injected props if skip becomes true", async () => { let count = 0; const query: DocumentNode = gql` query people($first: Int) { @@ -750,13 +778,13 @@ describe('[queries] skip', () => { } `; - const data1 = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data1 = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; const variables1 = { first: 1 }; - const data2 = { allPeople: { people: [{ name: 'Leia Skywalker' }] } }; + const data2 = { allPeople: { people: [{ name: "Leia Skywalker" }] } }; const variables2 = { first: 2 }; - const data3 = { allPeople: { people: [{ name: 'Anakin Skywalker' }] } }; + const data3 = { allPeople: { people: [{ name: "Anakin Skywalker" }] } }; const variables3 = { first: 3 }; type Data = typeof data1; @@ -770,18 +798,17 @@ describe('[queries] skip', () => { const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const Container = graphql(query, { - skip: () => count === 1 + 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 === 0) expect(data!.allPeople).toEqual(data1.allPeople); if (count === 1) expect(data).toBeUndefined(); if (count === 2 && !data!.loading) { expect(data!.allPeople).toEqual(data3.allPeople); @@ -823,7 +850,7 @@ describe('[queries] skip', () => { }); }); - itAsync('allows you to unmount a skipped query', (resolve, reject) => { + itAsync("allows you to unmount a skipped query", (resolve, reject) => { const query: DocumentNode = gql` query people { allPeople(first: 1) { @@ -836,7 +863,7 @@ describe('[queries] skip', () => { const link = mockSingleLink(); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Props { @@ -845,7 +872,7 @@ describe('[queries] skip', () => { let done = false; const Container = graphql(query, { - skip: true + skip: true, })( class extends React.Component> { componentDidMount() { diff --git a/src/react/hoc/__tests__/queries/updateQuery.test.tsx b/src/react/hoc/__tests__/queries/updateQuery.test.tsx index 14b25360e7b..0d3056b862e 100644 --- a/src/react/hoc/__tests__/queries/updateQuery.test.tsx +++ b/src/react/hoc/__tests__/queries/updateQuery.test.tsx @@ -1,18 +1,18 @@ -import React from 'react'; -import gql from 'graphql-tag'; -import { DocumentNode } from 'graphql'; -import { render, waitFor } from '@testing-library/react'; +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'; +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', () => { +describe("[queries] updateQuery", () => { // updateQuery - itAsync('exposes updateQuery as part of the props api', (resolve, reject) => { + itAsync("exposes updateQuery as part of the props api", (resolve, reject) => { const query: DocumentNode = gql` query people { allPeople(first: 1) { @@ -24,11 +24,11 @@ describe('[queries] updateQuery', () => { `; const link = mockSingleLink({ request: { query }, - result: { data: { allPeople: { people: [{ name: 'Luke Skywalker' }] } } } + result: { data: { allPeople: { people: [{ name: "Luke Skywalker" }] } } }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let done = false; @@ -39,7 +39,9 @@ describe('[queries] updateQuery', () => { expect(data!.updateQuery).toBeTruthy(); expect(data!.updateQuery instanceof Function).toBeTruthy(); try { - data!.updateQuery(() => { done = true; }); + data!.updateQuery(() => { + done = true; + }); } catch (error) { reject(error); } @@ -59,213 +61,229 @@ describe('[queries] updateQuery', () => { 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 + 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 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; + 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( - - - - ); + render( + + + + ); - waitFor(() => { - expect(done).toBe(true) - }).then(resolve, reject); - }); + 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 + 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 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) { - // TODO: branch never hit in test - expect(e.toString()).toMatch( - /ObservableQuery with this id doesn't exist:/ - ); - } - renderCount += 1; + 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) { + // TODO: branch never hit in test + expect(e.toString()).toMatch( + /ObservableQuery with this id doesn't exist:/ + ); + } + renderCount += 1; - return null; + return null; + } } - } - ); + ); - render( - - - - ); + render( + + + + ); - waitFor(() => { - expect(renderCount).toBe(2) - }).then(resolve, reject); - }); + 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 + 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 }) - }); + `; + 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; - }); + 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() { - this.updateQuery = this.props.data!.updateQuery; - return null; - } - } - ); + ); - render( - - - - ); + render( + + + + ); - waitFor(() => { - expect(done).toBe(true); - }).then(resolve, reject); - }); + 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 + 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 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 }) - }); + 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; - }); + 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() { - return null; - } - } - ); + ); - render( - - - - ); + render( + + + + ); - waitFor(() => { - expect(done).toBe(true) - }).then(resolve, reject); - }); + 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 index b7195be84f8..efd0b12924c 100644 --- a/src/react/hoc/__tests__/shared-operations.test.tsx +++ b/src/react/hoc/__tests__/shared-operations.test.tsx @@ -1,41 +1,41 @@ -import React from 'react'; -import { render, cleanup } 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'; +import React from "react"; +import { render, cleanup } 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[]) { + return function (...args: any[]) { const [firstFunction, ...restFunctions] = functions; let result = firstFunction.apply(null, args); - restFunctions.forEach(fnc => { + restFunctions.forEach((fnc) => { result = fnc.call(null, result); }); return result; }; } -describe('shared operations', () => { +describe("shared operations", () => { afterEach(cleanup); - describe('withApollo', () => { - it('passes apollo-client to props', () => { + describe("withApollo", () => { + it("passes apollo-client to props", () => { const client = new ApolloClient({ link: new ApolloLink((o, f) => (f ? f(o) : null)), - cache: new Cache() + cache: new Cache(), }); @withApollo - class ContainerWithData extends React.Component { + class ContainerWithData extends React.Component { render(): React.ReactNode { expect(this.props.client).toEqual(client); return null; @@ -50,7 +50,7 @@ describe('shared operations', () => { }); }); - it('binds two queries to props', () => { + it("binds two queries to props", () => { const peopleQuery: DocumentNode = gql` query people { allPeople(first: 1) { @@ -60,7 +60,7 @@ describe('shared operations', () => { } } `; - const peopleData = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const peopleData = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; interface PeopleData { allPeople: { people: [{ name: string }] }; } @@ -74,7 +74,7 @@ describe('shared operations', () => { } } `; - const shipsData = { allships: { ships: [{ name: 'Tie Fighter' }] } }; + const shipsData = { allships: { ships: [{ name: "Tie Fighter" }] } }; interface ShipsData { allShips: { ships: [{ name: string }] }; } @@ -85,7 +85,7 @@ describe('shared operations', () => { ); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface PeopleChildProps { @@ -97,7 +97,7 @@ describe('shared operations', () => { const withPeople: any = graphql<{}, PeopleData, {}, PeopleChildProps>( peopleQuery, { - name: 'people' + name: "people", } ); @@ -107,7 +107,7 @@ describe('shared operations', () => { const withShips: any = graphql<{}, ShipsData, {}, ShipsChildProps>( shipsQuery, { - name: 'ships' + name: "ships", } ); @@ -134,7 +134,7 @@ describe('shared operations', () => { unmount(); }); - it('binds two queries to props with different syntax', () => { + it("binds two queries to props with different syntax", () => { const peopleQuery: DocumentNode = gql` query people { allPeople(first: 1) { @@ -144,7 +144,7 @@ describe('shared operations', () => { } } `; - const peopleData = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const peopleData = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; interface PeopleData { allPeople: { people: [{ name: string }] }; } @@ -157,7 +157,7 @@ describe('shared operations', () => { } } `; - const shipsData = { allships: { ships: [{ name: 'Tie Fighter' }] } }; + const shipsData = { allships: { ships: [{ name: "Tie Fighter" }] } }; interface ShipsData { allShips: { ships: [{ name: string }] }; } @@ -168,7 +168,7 @@ describe('shared operations', () => { ); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface PeopleChildProps { @@ -178,7 +178,7 @@ describe('shared operations', () => { const withPeople = graphql<{}, PeopleData, {}, PeopleChildProps>( peopleQuery, { - name: 'people' + name: "people", } ); @@ -191,7 +191,7 @@ describe('shared operations', () => { {}, ShipsAndPeopleChildProps >(shipsQuery, { - name: 'ships' + name: "ships", }); const ContainerWithData = withPeople( @@ -216,7 +216,7 @@ describe('shared operations', () => { unmount(); }); - it('binds two operations to props', () => { + it("binds two operations to props", () => { const peopleQuery: DocumentNode = gql` query people { allPeople(first: 1) { @@ -226,7 +226,7 @@ describe('shared operations', () => { } } `; - const peopleData = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const peopleData = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; const peopleMutation: DocumentNode = gql` mutation addPerson { @@ -238,23 +238,23 @@ describe('shared operations', () => { } `; const peopleMutationData = { - allPeople: { people: [{ name: 'Leia Skywalker' }] } + allPeople: { people: [{ name: "Leia Skywalker" }] }, }; const link = mockSingleLink( { request: { query: peopleQuery }, result: { data: peopleData } }, { request: { query: peopleMutation }, - result: { data: peopleMutationData } + result: { data: peopleMutationData }, } ); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); - const withPeople = graphql(peopleQuery, { name: 'people' }); - const withPeopleMutation = graphql(peopleMutation, { name: 'addPerson' }); + const withPeople = graphql(peopleQuery, { name: "people" }); + const withPeopleMutation = graphql(peopleMutation, { name: "addPerson" }); const ContainerWithData = withPeople( withPeopleMutation( @@ -281,7 +281,7 @@ describe('shared operations', () => { unmount(); }); - itAsync('allows options to take an object', (resolve, reject) => { + itAsync("allows options to take an object", (resolve, reject) => { const query: DocumentNode = gql` query people { allPeople(first: 1) { @@ -291,16 +291,16 @@ describe('shared operations', () => { } } `; - const data = { allPeople: { people: [{ name: 'Luke Skywalker' }] } }; + const data = { allPeople: { people: [{ name: "Luke Skywalker" }] } }; type Data = typeof data; const link = mockSingleLink({ request: { query }, - result: { data } + result: { data }, }); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let queryExecuted = false; @@ -327,12 +327,12 @@ describe('shared operations', () => { resolve(); return; } - reject(new Error('query ran even though skip present')); + reject(new Error("query ran even though skip present")); }, 25); }); - describe('compose', () => { - it('binds two queries to props with different syntax', () => { + describe("compose", () => { + it("binds two queries to props with different syntax", () => { const peopleQuery: DocumentNode = gql` query people { allPeople(first: 1) { @@ -343,7 +343,7 @@ describe('shared operations', () => { } `; const peopleData = { - allPeople: { people: [{ name: 'Luke Skywalker' }] } + allPeople: { people: [{ name: "Luke Skywalker" }] }, }; type PeopleData = typeof peopleData; @@ -357,7 +357,7 @@ describe('shared operations', () => { } } `; - const shipsData = { allships: { ships: [{ name: 'Tie Fighter' }] } }; + const shipsData = { allships: { ships: [{ name: "Tie Fighter" }] } }; type ShipsData = typeof shipsData; @@ -367,7 +367,7 @@ describe('shared operations', () => { ); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface PeopleChildProps { @@ -381,12 +381,12 @@ describe('shared operations', () => { const enhanced = compose( graphql<{}, PeopleData, {}, PeopleChildProps>(peopleQuery, { - name: 'people' + name: "people", }), graphql( shipsQuery, { - name: 'ships' + name: "ships", } ) ); diff --git a/src/react/hoc/__tests__/ssr/getDataFromTree.test.tsx b/src/react/hoc/__tests__/ssr/getDataFromTree.test.tsx index 101961e1638..b4a0ab6fc6c 100644 --- a/src/react/hoc/__tests__/ssr/getDataFromTree.test.tsx +++ b/src/react/hoc/__tests__/ssr/getDataFromTree.test.tsx @@ -1,21 +1,21 @@ /** @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 } 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`', () => { +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 } 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; @@ -25,7 +25,7 @@ describe('SSR', () => { console.warn = consoleWarn; }); - it('should run through all of the queries that want SSR', async () => { + it("should run through all of the queries that want SSR", async () => { const query = gql` { currentUser { @@ -33,14 +33,14 @@ describe('SSR', () => { } } `; - const data1 = { currentUser: { firstName: 'James' } }; + const data1 = { currentUser: { firstName: "James" } }; const link = mockSingleLink({ request: { query }, result: { data: data1 }, }); const apolloClient = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Props {} @@ -49,15 +49,15 @@ describe('SSR', () => { firstName: string; }; } - const WrappedElement = graphql( - query - )(({ data }: ChildProps) => ( -
- {!data || data.loading || !data.currentUser - ? 'loading' - : data.currentUser.firstName} -
- )); + const WrappedElement = graphql(query)( + ({ data }: ChildProps) => ( +
+ {!data || data.loading || !data.currentUser + ? "loading" + : data.currentUser.firstName} +
+ ) + ); const app = ( @@ -65,19 +65,19 @@ describe('SSR', () => { ); - await getDataFromTree(app).then(markup => { + await getDataFromTree(app).then((markup) => { expect(markup).toMatch(/James/); }); await getMarkupFromTree({ tree: app, - renderFunction: ReactDOM.renderToString - }).then(markup => { + renderFunction: ReactDOM.renderToString, + }).then((markup) => { expect(markup).toMatch(/James/); }); }); - it('should allow network-only fetchPolicy as an option and still render prefetched data', () => { + it("should allow network-only fetchPolicy as an option and still render prefetched data", () => { const query = gql` { currentUser { @@ -87,12 +87,12 @@ describe('SSR', () => { `; const link = mockSingleLink({ request: { query }, - result: { data: { currentUser: { firstName: 'James' } } }, + result: { data: { currentUser: { firstName: "James" } } }, }); const apolloClient = new ApolloClient({ link, cache: new Cache({ addTypename: false }), - ssrMode: true + ssrMode: true, }); interface Props {} @@ -102,11 +102,11 @@ describe('SSR', () => { }; } const WrappedElement = graphql(query, { - options: { fetchPolicy: 'network-only' } + options: { fetchPolicy: "network-only" }, })(({ data }: ChildProps) => (
{!data || data.loading || !data.currentUser - ? 'loading' + ? "loading" : data.currentUser.firstName}
)); @@ -117,12 +117,12 @@ describe('SSR', () => {
); - return getDataFromTree(app).then(markup => { + return getDataFromTree(app).then((markup) => { expect(markup).toMatch(/James/); }); }); - it('should allow cache-and-network fetchPolicy as an option and still render prefetched data', () => { + it("should allow cache-and-network fetchPolicy as an option and still render prefetched data", () => { const query = gql` { currentUser { @@ -132,11 +132,11 @@ describe('SSR', () => { `; const link = mockSingleLink({ request: { query }, - result: { data: { currentUser: { firstName: 'James' } } } + result: { data: { currentUser: { firstName: "James" } } }, }); const apolloClient = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Props {} @@ -146,10 +146,10 @@ describe('SSR', () => { }; } const WrappedElement = graphql(query, { - options: { fetchPolicy: 'cache-and-network' } + options: { fetchPolicy: "cache-and-network" }, })(({ data }: ChildProps) => (
- {data && data.currentUser ? data.currentUser.firstName : 'loading'} + {data && data.currentUser ? data.currentUser.firstName : "loading"}
)); @@ -159,12 +159,12 @@ describe('SSR', () => {
); - return getDataFromTree(app).then(markup => { + return getDataFromTree(app).then((markup) => { expect(markup).toMatch(/James/); }); }); - it('should pick up queries deep in the render tree', () => { + it("should pick up queries deep in the render tree", () => { const query = gql` { currentUser { @@ -174,11 +174,11 @@ describe('SSR', () => { `; const link = mockSingleLink({ request: { query }, - result: { data: { currentUser: { firstName: 'James' } } }, + result: { data: { currentUser: { firstName: "James" } } }, }); const apolloClient = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Props {} @@ -188,15 +188,15 @@ describe('SSR', () => { }; } - const WrappedElement = graphql( - query - )(({ data }: ChildProps) => ( -
- {!data || data.loading || !data.currentUser - ? 'loading' - : data.currentUser.firstName} -
- )); + const WrappedElement = graphql(query)( + ({ data }: ChildProps) => ( +
+ {!data || data.loading || !data.currentUser + ? "loading" + : data.currentUser.firstName} +
+ ) + ); const Page = () => (
@@ -213,12 +213,12 @@ describe('SSR', () => { ); - return getDataFromTree(app).then(markup => { + return getDataFromTree(app).then((markup) => { expect(markup).toMatch(/James/); }); }); - it('should handle nested queries that depend on each other', () => { + it("should handle nested queries that depend on each other", () => { const idQuery: DocumentNode = gql` { currentUser { @@ -226,7 +226,7 @@ describe('SSR', () => { } } `; - const idData = { currentUser: { id: '1234' } }; + const idData = { currentUser: { id: "1234" } }; const userQuery: DocumentNode = gql` query getUser($id: String) { user(id: $id) { @@ -234,8 +234,8 @@ describe('SSR', () => { } } `; - const variables = { id: '1234' }; - const userData = { user: { firstName: 'James' } }; + const variables = { id: "1234" }; + const userData = { user: { firstName: "James" } }; const link = mockSingleLink( { request: { query: idQuery }, result: { data: idData } }, { @@ -245,7 +245,7 @@ describe('SSR', () => { ); const apolloClient = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Props {} @@ -275,15 +275,15 @@ describe('SSR', () => { >(userQuery, { skip: ({ data }) => data!.loading, options: ({ data }) => ({ - variables: { id: data!.currentUser!.id } - }) + variables: { id: data!.currentUser!.id }, + }), }); - const Component: React.FunctionComponent>> = ({ - data - }) => ( + const Component: React.FunctionComponent< + React.PropsWithChildren> + > = ({ data }) => (
{!data || data.loading || !data.user - ? 'loading' + ? "loading" : data.user.firstName}
); @@ -296,12 +296,12 @@ describe('SSR', () => { ); - return getDataFromTree(app).then(markup => { + return getDataFromTree(app).then((markup) => { expect(markup).toMatch(/James/); }); }); - it.skip('should return the first of multiple errors thrown by nested wrapped components', () => { + it.skip("should return the first of multiple errors thrown by nested wrapped components", () => { const lastNameQuery = gql` { currentUser { @@ -328,9 +328,9 @@ describe('SSR', () => { result: { data: { currentUser: { - lastName: 'Tester' + lastName: "Tester", }, - } + }, }, }, { @@ -338,15 +338,15 @@ describe('SSR', () => { result: { data: { currentUser: { - firstName: 'James' + firstName: "James", }, - } + }, }, } ); const apolloClient = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Props {} @@ -354,19 +354,19 @@ describe('SSR', () => { type WithLastNameProps = ChildProps; const withLastName = graphql(lastNameQuery); - const fooError = new Error('foo'); + const fooError = new Error("foo"); const BorkedComponent = () => { throw fooError; }; const WrappedBorkedComponent = withLastName(BorkedComponent); - const ContainerComponent: React.FunctionComponent>> = ({ - data - }) => ( + const ContainerComponent: React.FunctionComponent< + React.PropsWithChildren> + > = ({ data }) => (
{!data || data.loading || !data.currentUser - ? 'loading' + ? "loading" : data.currentUser.lastName} @@ -385,16 +385,16 @@ describe('SSR', () => { return getDataFromTree(app).then( () => { - throw new Error('Should have thrown an error'); + throw new Error("Should have thrown an error"); }, - e => { - expect(e.toString()).toEqual('Error: foo'); + (e) => { + expect(e.toString()).toEqual("Error: foo"); expect(e).toBe(fooError); } ); }); - it('should handle errors thrown by queries', () => { + it("should handle errors thrown by queries", () => { const query = gql` { currentUser { @@ -404,11 +404,11 @@ describe('SSR', () => { `; const link = mockSingleLink({ request: { query }, - error: new Error('Failed to fetch'), + error: new Error("Failed to fetch"), }); const apolloClient = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Props {} @@ -417,11 +417,13 @@ describe('SSR', () => { firstName: string; }; } - const WrappedElement = graphql( - query - )(({ data }: ChildProps) => ( -
{!data || data.loading ? 'loading' : data.error?.toString()}
- )); + const WrappedElement = graphql(query)( + ({ data }: ChildProps) => ( +
+ {!data || data.loading ? "loading" : data.error?.toString()} +
+ ) + ); const Page = () => (
@@ -438,13 +440,13 @@ describe('SSR', () => { ); - return getDataFromTree(app).catch(e => { + return getDataFromTree(app).catch((e) => { expect(e).toBeTruthy(); expect(e.toString()).toMatch(/Failed to fetch/); }); }); - it('should correctly skip queries (deprecated)', () => { + it("should correctly skip queries (deprecated)", () => { const query = gql` { currentUser { @@ -454,11 +456,11 @@ describe('SSR', () => { `; const link = mockSingleLink({ request: { query }, - result: { data: { currentUser: { firstName: 'James' } } }, + result: { data: { currentUser: { firstName: "James" } } }, }); const apolloClient = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Props {} @@ -468,9 +470,9 @@ describe('SSR', () => { }; } const WrappedElement = graphql(query, { - skip: true + skip: true, })(({ data }: ChildProps) => ( -
{!data ? 'skipped' : 'dang'}
+
{!data ? "skipped" : "dang"}
)); const app = ( @@ -479,12 +481,12 @@ describe('SSR', () => { ); - return getDataFromTree(app).then(markup => { + return getDataFromTree(app).then((markup) => { expect(markup).toMatch(/skipped/); }); }); - it('should use the correct default props for a query', () => { + it("should use the correct default props for a query", () => { const query = gql` query user($id: ID) { currentUser(id: $id) { @@ -492,8 +494,8 @@ describe('SSR', () => { } } `; - const resultData = { currentUser: { firstName: 'James' } }; - const variables = { id: '1' }; + const resultData = { currentUser: { firstName: "James" } }; + const variables = { id: "1" }; const link = mockSingleLink({ request: { query, variables }, result: { data: resultData }, @@ -502,7 +504,7 @@ describe('SSR', () => { const cache = new Cache({ addTypename: false }); const apolloClient = new ApolloClient({ link, - cache + cache, }); interface Props { @@ -516,19 +518,19 @@ describe('SSR', () => { interface Variables { id: string; } - const Element = graphql( - query, - )(({ data }: ChildProps) => ( -
- {!data || data.loading || !data.currentUser - ? 'loading' - : data.currentUser.firstName} -
- )); + const Element = graphql(query)( + ({ data }: ChildProps) => ( +
+ {!data || data.loading || !data.currentUser + ? "loading" + : data.currentUser.firstName} +
+ ) + ); const app = ( - + ); @@ -541,85 +543,88 @@ describe('SSR', () => { }); }); - itAsync('should allow for setting state in a component', (resolve, reject) => { - const query = gql` - query user($id: ID) { - currentUser(id: $id) { - firstName + itAsync( + "should allow for setting state in a component", + (resolve, reject) => { + const query = gql` + query user($id: ID) { + currentUser(id: $id) { + firstName + } } + `; + const resultData = { currentUser: { firstName: "James" } }; + const variables = { id: "1" }; + const link = mockSingleLink({ + request: { query, variables }, + result: { data: resultData }, + }); + + const cache = new Cache({ addTypename: false }); + const apolloClient = new ApolloClient({ + link, + cache, + }); + + interface Props { + id: string; } - `; - const resultData = { currentUser: { firstName: 'James' } }; - const variables = { id: '1' }; - const link = mockSingleLink({ - request: { query, variables }, - result: { data: resultData }, - }); - - const cache = new Cache({ addTypename: false }); - const apolloClient = new ApolloClient({ - link, - cache - }); - - interface Props { - id: string; - } - interface Data { - currentUser: { - firstName: string; - }; - } - interface Variables { - id: string; - } - - class Element extends React.Component< - ChildProps, - { thing: number } - > { - state = { thing: 1 }; - - static getDerivedStateFromProps() { - return { - thing: 2 + interface Data { + currentUser: { + firstName: string; }; } - - render() { - const { data } = this.props; - expect(this.state.thing).toBe(2); - return ( -
- {!data || data.loading || !data.currentUser - ? 'loading' - : data.currentUser.firstName} -
- ); + interface Variables { + id: string; } - } - const ElementWithData = graphql(query)(Element); + class Element extends React.Component< + ChildProps, + { thing: number } + > { + state = { thing: 1 }; - const app = ( - - - - ); + static getDerivedStateFromProps() { + return { + thing: 2, + }; + } - getDataFromTree(app) - .then(() => { - const initialState = cache.extract(); - expect(initialState).toBeTruthy(); - expect( - initialState.ROOT_QUERY!['currentUser({"id":"1"})'] - ).toBeTruthy(); - resolve(); - }) - .catch(console.error); - }); + render() { + const { data } = this.props; + expect(this.state.thing).toBe(2); + return ( +
+ {!data || data.loading || !data.currentUser + ? "loading" + : data.currentUser.firstName} +
+ ); + } + } + + const ElementWithData = graphql(query)(Element); + + const app = ( + + + + ); + + getDataFromTree(app) + .then(() => { + const initialState = cache.extract(); + expect(initialState).toBeTruthy(); + expect( + initialState.ROOT_QUERY!['currentUser({"id":"1"})'] + ).toBeTruthy(); + resolve(); + }) + .catch(console.error); + } + ); - it('should correctly initialize an empty state to null', () => { + it("should correctly initialize an empty state to null", () => { class Element extends React.Component { render() { expect(this.state).toBeNull(); @@ -630,15 +635,15 @@ describe('SSR', () => { return getDataFromTree(); }); - it('should maintain any state set in the element constructor', () => { + it("should maintain any state set in the element constructor", () => { class Element extends React.Component<{}, { foo: string }> { constructor(props: {}) { super(props); - this.state = { foo: 'bar' }; + this.state = { foo: "bar" }; } render() { - expect(this.state).toEqual({ foo: 'bar' }); + expect(this.state).toEqual({ foo: "bar" }); return null; } } @@ -646,7 +651,7 @@ describe('SSR', () => { return getDataFromTree(); }); - itAsync('should allow prepping state from props', (resolve, reject) => { + itAsync("should allow prepping state from props", (resolve, reject) => { const query = gql` query user($id: ID) { currentUser(id: $id) { @@ -654,8 +659,8 @@ describe('SSR', () => { } } `; - const resultData = { currentUser: { firstName: 'James' } }; - const variables = { id: '1' }; + const resultData = { currentUser: { firstName: "James" } }; + const variables = { id: "1" }; const link = mockSingleLink({ request: { query, variables }, result: { data: resultData }, @@ -663,8 +668,8 @@ describe('SSR', () => { const apolloClient = new ApolloClient({ link, cache: new Cache({ - addTypename: false - }) + addTypename: false, + }), }); interface Props { id: string; @@ -691,14 +696,14 @@ describe('SSR', () => { state: State = { thing: 1, userId: null, - client: null + client: null, }; static getDerivedStateFromProps(props: Props, state: State) { return { thing: state.thing + 1, userId: props.id, - client: apolloClient + client: apolloClient, }; } @@ -710,7 +715,7 @@ describe('SSR', () => { return (
{!data || data.loading || !data.currentUser - ? 'loading' + ? "loading" : data.currentUser.firstName}
); @@ -721,7 +726,7 @@ describe('SSR', () => { const app = ( - + ); @@ -745,8 +750,8 @@ describe('SSR', () => { } } `; - const resultData = { currentUser: { firstName: 'James' } }; - const variables = { id: '1' }; + const resultData = { currentUser: { firstName: "James" } }; + const variables = { id: "1" }; const link = mockSingleLink({ request: { query, variables }, result: { data: resultData }, @@ -755,7 +760,7 @@ describe('SSR', () => { const cache = new Cache({ addTypename: false }); const apolloClient = new ApolloClient({ link, - cache + cache, }); interface Data { @@ -777,18 +782,18 @@ describe('SSR', () => { } const Element = graphql(query, { - options: props => ({ variables: props, ssr: false }) + options: (props) => ({ variables: props, ssr: false }), })(({ data }) => (
{!data || data.loading || !data.currentUser - ? 'loading' + ? "loading" : data.currentUser.firstName}
)); const app = ( - + ); @@ -807,8 +812,8 @@ describe('SSR', () => { } } `; - const resultData = { currentUser: { firstName: 'James' } }; - const variables = { id: '1' }; + const resultData = { currentUser: { firstName: "James" } }; + const variables = { id: "1" }; const link = mockSingleLink({ request: { query, variables }, result: { data: resultData }, @@ -817,7 +822,7 @@ describe('SSR', () => { const cache = new Cache({ addTypename: false }); const apolloClient = new ApolloClient({ link, - cache + cache, }); interface Data { @@ -830,7 +835,7 @@ describe('SSR', () => { {({ data, loading }: { data: Data; loading: boolean }) => (
- {loading || !data ? 'loading' : data.currentUser!.firstName} + {loading || !data ? "loading" : data.currentUser!.firstName}
)}
@@ -838,7 +843,7 @@ describe('SSR', () => { const app = ( - + ); @@ -849,7 +854,7 @@ describe('SSR', () => { }); }); - it('should correctly handle SSR mutations', () => { + it("should correctly handle SSR mutations", () => { const query = gql` { currentUser { @@ -857,7 +862,7 @@ describe('SSR', () => { } } `; - const data1 = { currentUser: { firstName: 'James' } }; + const data1 = { currentUser: { firstName: "James" } }; const mutation = gql` mutation { @@ -866,7 +871,7 @@ describe('SSR', () => { } } `; - const mutationData = { logRoutes: { id: 'foo' } }; + const mutationData = { logRoutes: { id: "foo" } }; const link = mockSingleLink( { request: { query }, result: { data: data1 } }, @@ -877,7 +882,7 @@ describe('SSR', () => { ); const apolloClient = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Data { @@ -900,9 +905,9 @@ describe('SSR', () => { expect(data!.refetch).toBeTruthy(); return { refetchQuery: data!.refetch, - data: data! + data: data!, }; - } + }, } ); @@ -913,23 +918,29 @@ describe('SSR', () => { { action: (variables: {}) => Promise } >(mutation, { props: ({ ownProps, mutate }: any) => { - if (ownProps.loading || typeof ownProps.loading === 'undefined') + if (ownProps.loading || typeof ownProps.loading === "undefined") return { action: () => Promise.resolve() }; expect(ownProps.refetchQuery).toBeTruthy(); return { action(variables: {}) { return mutate!({ variables }).then(() => ownProps.refetchQuery()); - } + }, }; - } + }, }); - const Element: React.FunctionComponent Promise; - }>>> = ({ data }) => ( + const Element: React.FunctionComponent< + React.PropsWithChildren< + React.PropsWithChildren< + QueryChildProps & { + action: (variables: {}) => Promise; + } + > + > + > = ({ data }) => (
{!data || data.loading || !data.currentUser - ? 'loading' + ? "loading" : data.currentUser.firstName}
); @@ -942,12 +953,12 @@ describe('SSR', () => { ); - return getDataFromTree(app).then(markup => { + return getDataFromTree(app).then((markup) => { expect(markup).toMatch(/James/); }); }); - it('should correctly handle SSR mutations, reverse order', () => { + it("should correctly handle SSR mutations, reverse order", () => { const query = gql` { currentUser { @@ -979,16 +990,16 @@ describe('SSR', () => { const link = mockSingleLink( { request: { query }, - result: { data: { currentUser: { firstName: 'James' } } }, + result: { data: { currentUser: { firstName: "James" } } }, }, { request: { query: mutation }, - result: { data: { logRoutes: { id: 'foo' } } }, + result: { data: { logRoutes: { id: "foo" } } }, } ); const apolloClient = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); const withMutation = graphql(mutation); @@ -999,19 +1010,21 @@ describe('SSR', () => { props: ({ ownProps, data }) => { expect(ownProps.mutate).toBeTruthy(); return { - data + data, }; - } + }, }); - const Element: React.FunctionComponent, - QueryData, - {} - >>>> = ({ data }) => ( + const Element: React.FunctionComponent< + React.PropsWithChildren< + React.PropsWithChildren< + ChildProps, QueryData, {}> + > + > + > = ({ data }) => (
{!data || data.loading || !data.currentUser - ? 'loading' + ? "loading" : data.currentUser.firstName}
); @@ -1024,12 +1037,12 @@ describe('SSR', () => { ); - return getDataFromTree(app).then(markup => { + return getDataFromTree(app).then((markup) => { expect(markup).toMatch(/James/); }); }); - it('should not require `ApolloProvider` to be the root component', () => { + it("should not require `ApolloProvider` to be the root component", () => { const query = gql` { currentUser { @@ -1045,27 +1058,30 @@ describe('SSR', () => { const link = mockSingleLink({ request: { query }, - result: { data: { currentUser: { firstName: 'James' } } }, + result: { data: { currentUser: { firstName: "James" } } }, }); const apolloClient = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); - const WrappedElement = graphql<{}, Data>( - query - )(({ data }: ChildProps<{}, Data>) => ( -
- {!data || data.loading || !data.currentUser - ? 'loading' - : data.currentUser.firstName} -
- )); + const WrappedElement = graphql<{}, Data>(query)( + ({ data }: ChildProps<{}, Data>) => ( +
+ {!data || data.loading || !data.currentUser + ? "loading" + : data.currentUser.firstName} +
+ ) + ); - class MyRootContainer extends React.Component { + class MyRootContainer extends React.Component< + React.PropsWithChildren, + { color: string } + > { constructor(props: {}) { super(props); - this.state = { color: 'purple' }; + this.state = { color: "purple" }; } getChildContext() { @@ -1078,7 +1094,7 @@ describe('SSR', () => { } (MyRootContainer as any).childContextTypes = { - color: PropTypes.string + color: PropTypes.string, }; const app = ( @@ -1089,7 +1105,7 @@ describe('SSR', () => { ); - return getDataFromTree(app).then(markup => { + return getDataFromTree(app).then((markup) => { expect(markup).toMatch(/James/); }); }); diff --git a/src/react/hoc/__tests__/ssr/server.test.tsx b/src/react/hoc/__tests__/ssr/server.test.tsx index a1322817ca3..e911a754df5 100644 --- a/src/react/hoc/__tests__/ssr/server.test.tsx +++ b/src/react/hoc/__tests__/ssr/server.test.tsx @@ -1,5 +1,5 @@ /** @jest-environment node */ -import React from 'react'; +import React from "react"; import { print, graphql as execute, @@ -8,134 +8,136 @@ import { GraphQLList, GraphQLString, GraphQLID, - DocumentNode -} from 'graphql'; -import gql from 'graphql-tag'; + DocumentNode, +} from "graphql"; +import gql from "graphql-tag"; -import { ApolloClient } from '../../../../core'; -import { ApolloProvider } from '../../../context'; -import { InMemoryCache as Cache } from '../../../../cache'; -import { ApolloLink } from '../../../../link/core'; -import { Observable } from '../../../../utilities'; -import { renderToStringWithData } from '../../../ssr'; -import { graphql } from '../../graphql'; -import { ChildProps } from '../../types'; +import { ApolloClient } from "../../../../core"; +import { ApolloProvider } from "../../../context"; +import { InMemoryCache as Cache } from "../../../../cache"; +import { ApolloLink } from "../../../../link/core"; +import { Observable } from "../../../../utilities"; +import { renderToStringWithData } from "../../../ssr"; +import { graphql } from "../../graphql"; +import { ChildProps } from "../../types"; -const planetMap = new Map([['Planet:1', { id: 'Planet:1', name: 'Tatooine' }]]); +const planetMap = new Map([["Planet:1", { id: "Planet:1", name: "Tatooine" }]]); const shipMap = new Map([ [ - 'Ship:2', + "Ship:2", { - id: 'Ship:2', - name: 'CR90 corvette', - films: ['Film:4', 'Film:6', 'Film:3'] - } + id: "Ship:2", + name: "CR90 corvette", + films: ["Film:4", "Film:6", "Film:3"], + }, ], [ - 'Ship:3', + "Ship:3", { - id: 'Ship:3', - name: 'Star Destroyer', - films: ['Film:4', 'Film:5', 'Film:6'] - } - ] + 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' }] + ["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', + name: "Planet", fields: { id: { type: GraphQLID }, - name: { type: GraphQLString } - } + name: { type: GraphQLString }, + }, }); const FilmType = new GraphQLObjectType({ - name: 'Film', + name: "Film", fields: { id: { type: GraphQLID }, - title: { type: GraphQLString } - } + title: { type: GraphQLString }, + }, }); const ShipType = new GraphQLObjectType({ - name: 'Ship', + name: "Ship", fields: { id: { type: GraphQLID }, name: { type: GraphQLString }, films: { type: new GraphQLList(FilmType), - resolve: ({ films }) => films.map((id: string) => filmMap.get(id)) - } - } + resolve: ({ films }) => films.map((id: string) => filmMap.get(id)), + }, + }, }); const QueryType = new GraphQLObjectType({ - name: 'Query', + name: "Query", fields: { allPlanets: { type: new GraphQLList(PlanetType), - resolve: () => Array.from(planetMap.values()) + resolve: () => Array.from(planetMap.values()), }, allShips: { type: new GraphQLList(ShipType), - resolve: () => Array.from(shipMap.values()) + resolve: () => Array.from(shipMap.values()), }, ship: { type: ShipType, args: { id: { type: GraphQLID } }, - resolve: (_, { id }) => shipMap.get(id) + resolve: (_, { id }) => shipMap.get(id), }, film: { type: FilmType, args: { id: { type: GraphQLID } }, - resolve: (_, { id }) => filmMap.get(id) - } - } + resolve: (_, { id }) => filmMap.get(id), + }, + }, }); const Schema = new GraphQLSchema({ query: QueryType }); -describe('SSR', () => { - describe('`renderToStringWithData`', () => { +describe("SSR", () => { + describe("`renderToStringWithData`", () => { // XXX break into smaller tests // XXX mock all queries - it('should work on a non trivial example', function() { + it("should work on a non trivial example", function () { const apolloClient = new ApolloClient({ - link: new ApolloLink(config => { - return new Observable(observer => { + link: new ApolloLink((config) => { + return new Observable((observer) => { execute({ schema: Schema, source: print(config.query), variableValues: config.variables, - operationName: config.operationName + operationName: config.operationName, }) - .then(result => { + .then((result) => { observer.next(result); observer.complete(); }) - .catch(e => { + .catch((e) => { observer.error(e); }); }); }), - cache: new Cache() + cache: new Cache(), }); - @graphql(gql` - query data($id: ID!) { - film(id: $id) { - title + @graphql( + gql` + query data($id: ID!) { + film(id: $id) { + title + } } - } - ` as DocumentNode) + ` as DocumentNode + ) class Film extends React.Component { render(): React.ReactNode { const { data } = this.props; @@ -156,16 +158,18 @@ describe('SSR', () => { id: string; } - @graphql(gql` - query data($id: ID!) { - ship(id: $id) { - name - films { - id + @graphql( + gql` + query data($id: ID!) { + ship(id: $id) { + name + films { + id + } } } - } - ` as DocumentNode) + ` as DocumentNode + ) class Starship extends React.Component< ChildProps > { @@ -193,13 +197,15 @@ describe('SSR', () => { allShips: { id: string }[]; } - @graphql<{}, AllShipsData>(gql` - query data { - allShips { - id + @graphql<{}, AllShipsData>( + gql` + query data { + allShips { + id + } } - } - ` as DocumentNode) + ` as DocumentNode + ) class AllShips extends React.Component> { render(): React.ReactNode { const { data } = this.props; @@ -222,13 +228,15 @@ describe('SSR', () => { allPlanets: { name: string }[]; } - @graphql<{}, AllPlanetsData>(gql` - query data { - allPlanets { - name + @graphql<{}, AllPlanetsData>( + gql` + query data { + allPlanets { + name + } } - } - ` as DocumentNode) + ` as DocumentNode + ) class AllPlanets extends React.Component> { render(): React.ReactNode { const { data } = this.props; @@ -267,7 +275,7 @@ describe('SSR', () => { ); - return renderToStringWithData(app).then(markup => { + return renderToStringWithData(app).then((markup) => { expect(markup).toMatch(/CR90 corvette/); expect(markup).toMatch(/Return of the Jedi/); expect(markup).toMatch(/A New Hope/); diff --git a/src/react/hoc/__tests__/statics.test.tsx b/src/react/hoc/__tests__/statics.test.tsx index 9c7150d7a6d..45b321053b5 100644 --- a/src/react/hoc/__tests__/statics.test.tsx +++ b/src/react/hoc/__tests__/statics.test.tsx @@ -1,7 +1,7 @@ -import React from 'react'; -import gql from 'graphql-tag'; +import React from "react"; +import gql from "graphql-tag"; -import { graphql } from '../graphql'; +import { graphql } from "../graphql"; let sampleOperation = gql` { @@ -11,33 +11,33 @@ let sampleOperation = gql` } `; -describe('statics', () => { - it('should be preserved', () => { +describe("statics", () => { + it("should be preserved", () => { const ApolloContainer = graphql(sampleOperation)( class extends React.Component { - static veryStatic = 'such global'; + static veryStatic = "such global"; } ); - expect((ApolloContainer as any).veryStatic).toBe('such global'); + expect((ApolloContainer as any).veryStatic).toBe("such global"); }); - it('exposes a debuggable displayName', () => { + it("exposes a debuggable displayName", () => { @graphql(sampleOperation) class ApolloContainer extends React.Component {} expect((ApolloContainer as any).displayName).toBe( - 'Apollo(ApolloContainer)' + "Apollo(ApolloContainer)" ); }); - it('honors custom display names', () => { + it("honors custom display names", () => { const ApolloContainer = graphql(sampleOperation)( class extends React.Component { - static displayName = 'Foo'; + static displayName = "Foo"; } ); - expect((ApolloContainer as any).displayName).toBe('Apollo(Foo)'); + expect((ApolloContainer as any).displayName).toBe("Apollo(Foo)"); }); }); diff --git a/src/react/hoc/__tests__/subscriptions/subscriptions.test.tsx b/src/react/hoc/__tests__/subscriptions/subscriptions.test.tsx index d3c59d582ac..b81567ca0d5 100644 --- a/src/react/hoc/__tests__/subscriptions/subscriptions.test.tsx +++ b/src/react/hoc/__tests__/subscriptions/subscriptions.test.tsx @@ -1,19 +1,19 @@ -import React from 'react'; -import { act, 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, MockSubscriptionLink } from '../../../../testing'; -import { graphql } from '../../graphql'; -import { ChildProps } from '../../types'; +import React from "react"; +import { act, 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, MockSubscriptionLink } from "../../../../testing"; +import { graphql } from "../../graphql"; +import { ChildProps } from "../../types"; const IS_REACT_18 = React.version.startsWith("18"); -describe('subscriptions', () => { +describe("subscriptions", () => { let error: typeof console.error; beforeEach(() => { @@ -27,16 +27,16 @@ describe('subscriptions', () => { }); const results = [ - 'James Baxley', - 'John Pinkerton', - 'Sam Claridge', - 'Ben Coleman' - ].map(name => ({ + "James Baxley", + "John Pinkerton", + "Sam Claridge", + "Ben Coleman", + ].map((name) => ({ result: { data: { user: { name } } }, - delay: 10 + delay: 10, })); - it('binds a subscription to props', () => { + it("binds a subscription to props", () => { const query: DocumentNode = gql` subscription UserInfo { user { @@ -47,7 +47,7 @@ describe('subscriptions', () => { const link = new MockSubscriptionLink(); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Props {} @@ -55,14 +55,14 @@ describe('subscriptions', () => { user: { name: string }; } - const ContainerWithData = graphql(query)( - ({ data }: ChildProps) => { - expect(data).toBeTruthy(); - expect(data!.user).toBeFalsy(); - expect(data!.loading).toBeTruthy(); - return null; - } - ); + const ContainerWithData = graphql(query)(({ + data, + }: ChildProps) => { + expect(data).toBeTruthy(); + expect(data!.user).toBeFalsy(); + expect(data!.loading).toBeTruthy(); + return null; + }); render( @@ -71,7 +71,7 @@ describe('subscriptions', () => { ); }); - it('includes the variables in the props', () => { + it("includes the variables in the props", () => { const query: DocumentNode = gql` subscription UserInfo($name: String) { user(name: $name) { @@ -79,11 +79,11 @@ describe('subscriptions', () => { } } `; - const variables = { name: 'James Baxley' }; + const variables = { name: "James Baxley" }; const link = new MockSubscriptionLink(); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); interface Variables { @@ -94,22 +94,22 @@ describe('subscriptions', () => { user: { name: string }; } - const ContainerWithData = graphql(query)( - ({ data }: ChildProps) => { - expect(data).toBeTruthy(); - expect(data!.variables).toEqual(variables); - return null; - } - ); + const ContainerWithData = graphql(query)(({ + data, + }: ChildProps) => { + expect(data).toBeTruthy(); + expect(data!.variables).toEqual(variables); + return null; + }); render( - + ); }); - itAsync('does not swallow children errors', (resolve, reject) => { + itAsync("does not swallow children errors", (resolve, reject) => { const query: DocumentNode = gql` subscription UserInfo { user { @@ -120,7 +120,7 @@ describe('subscriptions', () => { const link = new MockSubscriptionLink(); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let bar: any; @@ -151,7 +151,7 @@ describe('subscriptions', () => { ); }); - itAsync('executes a subscription', (resolve, reject) => { + itAsync("executes a subscription", (resolve, reject) => { jest.useFakeTimers(); const query: DocumentNode = gql` @@ -168,7 +168,7 @@ describe('subscriptions', () => { const link = new MockSubscriptionLink(); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let count = 0; @@ -218,34 +218,34 @@ describe('subscriptions', () => { act(() => { jest.advanceTimersByTime(230); - }) + }); }); - itAsync('resubscribes to a subscription', (resolve, reject) => { + itAsync("resubscribes to a subscription", (resolve, reject) => { //we make an extra Hoc which will trigger the inner HoC to resubscribe //these are the results for the outer subscription const triggerResults = [ - '0', - 'trigger resubscribe', - '3', - '4', - '5', - '6', - '7' - ].map(trigger => ({ + "0", + "trigger resubscribe", + "3", + "4", + "5", + "6", + "7", + ].map((trigger) => ({ result: { data: { trigger } }, - delay: 10 + delay: 10, })); //These are the results from the resubscription const results3 = [ - 'NewUser: 1', - 'NewUser: 2', - 'NewUser: 3', - 'NewUser: 4' - ].map(name => ({ + "NewUser: 1", + "NewUser: 2", + "NewUser: 3", + "NewUser: 4", + ].map((name) => ({ result: { data: { user: { name } } }, - delay: 10 + delay: 10, })); const query: DocumentNode = gql` @@ -271,14 +271,14 @@ describe('subscriptions', () => { const userLink = new MockSubscriptionLink(); const triggerLink = new MockSubscriptionLink(); const link = new ApolloLink((o, f) => (f ? f(o) : null)).split( - ({ operationName }) => operationName === 'UserInfo', + ({ operationName }) => operationName === "UserInfo", userLink, triggerLink ); const client = new ApolloClient({ link, - cache: new Cache({ addTypename: false }) + cache: new Cache({ addTypename: false }), }); let count = 0; @@ -288,9 +288,9 @@ describe('subscriptions', () => { const Container = graphql<{}, TriggerData>(triggerQuery)( graphql(query, { - shouldResubscribe: nextProps => { - return nextProps.data!.trigger === 'trigger resubscribe'; - } + shouldResubscribe: (nextProps) => { + return nextProps.data!.trigger === "trigger resubscribe"; + }, })( class Query extends React.Component { componentDidUpdate() { diff --git a/src/react/hoc/graphql.tsx b/src/react/hoc/graphql.tsx index 02220646731..88bf39e9961 100644 --- a/src/react/hoc/graphql.tsx +++ b/src/react/hoc/graphql.tsx @@ -1,18 +1,18 @@ -import type { DocumentNode } from 'graphql'; +import type { DocumentNode } from "graphql"; -import { parser, DocumentType } from '../parser/index.js'; -import { withQuery } from './query-hoc.js'; -import { withMutation } from './mutation-hoc.js'; -import { withSubscription } from './subscription-hoc.js'; -import type { OperationOption, DataProps, MutateProps } from './types.js'; -import type { OperationVariables } from '../../core/index.js'; +import { parser, DocumentType } from "../parser/index.js"; +import { withQuery } from "./query-hoc.js"; +import { withMutation } from "./mutation-hoc.js"; +import { withSubscription } from "./subscription-hoc.js"; +import type { OperationOption, DataProps, MutateProps } from "./types.js"; +import type { OperationVariables } from "../../core/index.js"; export function graphql< TProps extends TGraphQLVariables | {} = {}, TData extends object = {}, TGraphQLVariables extends OperationVariables = {}, TChildProps extends object = Partial> & - Partial> + Partial>, >( document: DocumentNode, operationOptions: OperationOption< diff --git a/src/react/hoc/hoc-utils.tsx b/src/react/hoc/hoc-utils.tsx index 11ec8f381d4..2e59f74e944 100644 --- a/src/react/hoc/hoc-utils.tsx +++ b/src/react/hoc/hoc-utils.tsx @@ -1,14 +1,14 @@ -import { invariant } from '../../utilities/globals/index.js'; -import * as React from 'react'; -import type { OperationVariables } from '../../core/index.js'; -import type { IDocumentDefinition } from '../parser/index.js'; +import { invariant } from "../../utilities/globals/index.js"; +import * as React from "react"; +import type { OperationVariables } from "../../core/index.js"; +import type { IDocumentDefinition } from "../parser/index.js"; export const defaultMapPropsToOptions = () => ({}); -export const defaultMapResultToProps:

(props: P) => P = props => props; +export const defaultMapResultToProps:

(props: P) => P = (props) => props; export const defaultMapPropsToSkip = () => false; export function getDisplayName

(WrappedComponent: React.ComponentType

) { - return WrappedComponent.displayName || WrappedComponent.name || 'Component'; + return WrappedComponent.displayName || WrappedComponent.name || "Component"; } export function calculateVariablesFromProps( @@ -22,13 +22,13 @@ export function calculateVariablesFromProps( const variableName = variable.name.value; const variableProp = (props as any)[variableName]; - if (typeof variableProp !== 'undefined') { + if (typeof variableProp !== "undefined") { variables[variableName] = variableProp; continue; } // Allow optional props - if (type.kind !== 'NonNullType') { + if (type.kind !== "NonNullType") { variables[variableName] = undefined; } } @@ -43,7 +43,7 @@ export type RefSetter = ( export class GraphQLBase< TProps, TChildProps, - TState = any + TState = any, > extends React.Component { public withRef: boolean = false; // wrapped instance diff --git a/src/react/hoc/index.ts b/src/react/hoc/index.ts index 0d75eba7de2..477bc234122 100644 --- a/src/react/hoc/index.ts +++ b/src/react/hoc/index.ts @@ -1,10 +1,10 @@ -import '../../utilities/globals/index.js'; +import "../../utilities/globals/index.js"; -export { graphql } from './graphql.js'; +export { graphql } from "./graphql.js"; -export { withQuery } from './query-hoc.js'; -export { withMutation } from './mutation-hoc.js'; -export { withSubscription } from './subscription-hoc.js'; -export { withApollo } from './withApollo.js'; +export { withQuery } from "./query-hoc.js"; +export { withMutation } from "./mutation-hoc.js"; +export { withSubscription } from "./subscription-hoc.js"; +export { withApollo } from "./withApollo.js"; -export * from './types.js'; +export * from "./types.js"; diff --git a/src/react/hoc/mutation-hoc.tsx b/src/react/hoc/mutation-hoc.tsx index 67f5a96184b..a0098a0f290 100644 --- a/src/react/hoc/mutation-hoc.tsx +++ b/src/react/hoc/mutation-hoc.tsx @@ -1,24 +1,24 @@ -import * as React from 'react'; -import type { DocumentNode } from 'graphql'; -import hoistNonReactStatics from 'hoist-non-react-statics'; +import * as React from "react"; +import type { DocumentNode } from "graphql"; +import hoistNonReactStatics from "hoist-non-react-statics"; -import { parser } from '../parser/index.js'; -import type { DefaultContext, OperationVariables } from '../../core/types.js'; +import { parser } from "../parser/index.js"; +import type { DefaultContext, OperationVariables } from "../../core/types.js"; import type { BaseMutationOptions, MutationFunction, - MutationResult -} from '../types/types.js'; -import { Mutation } from '../components/index.js'; + MutationResult, +} from "../types/types.js"; +import { Mutation } from "../components/index.js"; import { defaultMapPropsToOptions, getDisplayName, calculateVariablesFromProps, - GraphQLBase -} from './hoc-utils.js'; -import type { OperationOption, OptionProps, MutateProps } from './types.js'; -import type { ApolloCache } from '../../core/index.js'; + GraphQLBase, +} from "./hoc-utils.js"; +import type { OperationOption, OptionProps, MutateProps } from "./types.js"; +import type { ApolloCache } from "../../core/index.js"; export function withMutation< TProps extends TGraphQLVariables | {} = {}, @@ -40,14 +40,20 @@ export function withMutation< const operation = parser(document); // extract options - const { - options = defaultMapPropsToOptions, - alias = 'Apollo' - } = operationOptions; + const { options = defaultMapPropsToOptions, alias = "Apollo" } = + operationOptions; - let mapPropsToOptions = options as (props: any) => BaseMutationOptions; - if (typeof mapPropsToOptions !== 'function') - mapPropsToOptions = () => options as BaseMutationOptions; + let mapPropsToOptions = options as ( + props: any + ) => BaseMutationOptions; + if (typeof mapPropsToOptions !== "function") + mapPropsToOptions = () => + options as BaseMutationOptions< + TData, + TGraphQLVariables, + TContext, + TCache + >; return ( WrappedComponent: React.ComponentType @@ -58,16 +64,24 @@ export function withMutation< static WrappedComponent = WrappedComponent; render() { let props = this.props as TProps; - const opts = mapPropsToOptions(props) as BaseMutationOptions; + const opts = mapPropsToOptions(props) as BaseMutationOptions< + TData, + TGraphQLVariables, + TContext, + TCache + >; if (operationOptions.withRef) { this.withRef = true; props = Object.assign({}, props, { - ref: this.setWrappedInstance + ref: this.setWrappedInstance, }); } if (!opts.variables && operation.variables.length > 0) { - opts.variables = calculateVariablesFromProps(operation, props) as TGraphQLVariables; + opts.variables = calculateVariablesFromProps( + operation, + props + ) as TGraphQLVariables; } return ( @@ -81,24 +95,21 @@ export function withMutation< // we massage the Mutation component's shape here to replicate that // this matches the query HoC const result = Object.assign(r, data || {}); - const name = operationOptions.name || 'mutate'; + const name = operationOptions.name || "mutate"; const resultName = operationOptions.name ? `${name}Result` - : 'result'; - let childProps = ({ + : "result"; + let childProps = { [name]: mutate, - [resultName]: result - } as any) as TChildProps; + [resultName]: result, + } as any as TChildProps; if (operationOptions.props) { - const newResult: OptionProps< - TProps, - TData, - TGraphQLVariables - > = { - [name]: mutate, - [resultName]: result, - ownProps: props - }; + const newResult: OptionProps = + { + [name]: mutate, + [resultName]: result, + ownProps: props, + }; childProps = operationOptions.props(newResult) as any; } diff --git a/src/react/hoc/query-hoc.tsx b/src/react/hoc/query-hoc.tsx index 04cf7a2b3d5..144133494c6 100644 --- a/src/react/hoc/query-hoc.tsx +++ b/src/react/hoc/query-hoc.tsx @@ -1,24 +1,24 @@ -import * as React from 'react'; -import type { DocumentNode } from 'graphql'; -import hoistNonReactStatics from 'hoist-non-react-statics'; +import * as React from "react"; +import type { DocumentNode } from "graphql"; +import hoistNonReactStatics from "hoist-non-react-statics"; -import { parser } from '../parser/index.js'; -import type { BaseQueryOptions } from '../types/types.js'; -import { Query } from '../components/index.js'; +import { parser } from "../parser/index.js"; +import type { BaseQueryOptions } from "../types/types.js"; +import { Query } from "../components/index.js"; import { getDisplayName, GraphQLBase, calculateVariablesFromProps, defaultMapPropsToOptions, - defaultMapPropsToSkip -} from './hoc-utils.js'; -import type { OperationOption, OptionProps, DataProps } from './types.js'; + defaultMapPropsToSkip, +} from "./hoc-utils.js"; +import type { OperationOption, OptionProps, DataProps } from "./types.js"; export function withQuery< TProps extends TGraphQLVariables | Record = Record, TData extends object = {}, TGraphQLVariables extends object = {}, - TChildProps extends object = DataProps + TChildProps extends object = DataProps, >( document: DocumentNode, operationOptions: OperationOption< @@ -34,16 +34,16 @@ export function withQuery< const { options = defaultMapPropsToOptions, skip = defaultMapPropsToSkip, - alias = 'Apollo' + alias = "Apollo", } = operationOptions; let mapPropsToOptions = options as (props: any) => BaseQueryOptions; - if (typeof mapPropsToOptions !== 'function') { + if (typeof mapPropsToOptions !== "function") { mapPropsToOptions = () => options as BaseQueryOptions; } let mapPropsToSkip = skip as (props: any) => boolean; - if (typeof mapPropsToSkip !== 'function') { + if (typeof mapPropsToSkip !== "function") { mapPropsToSkip = () => skip as any; } @@ -79,7 +79,7 @@ export function withQuery< if (operationOptions.withRef) { this.withRef = true; props = Object.assign({}, props, { - ref: this.setWrappedInstance + ref: this.setWrappedInstance, }); } @@ -97,17 +97,14 @@ export function withQuery< // up onto the result since it was passed as a nested prop // we massage the Query components shape here to replicate that const result = Object.assign(r, data || {}); - const name = operationOptions.name || 'data'; + const name = operationOptions.name || "data"; let childProps = { [name]: result }; if (operationOptions.props) { - const newResult: OptionProps< - TProps, - TData, - TGraphQLVariables - > = { - [name]: result, - ownProps: props as TProps - }; + const newResult: OptionProps = + { + [name]: result, + ownProps: props as TProps, + }; lastResultProps = operationOptions.props( newResult, lastResultProps diff --git a/src/react/hoc/subscription-hoc.tsx b/src/react/hoc/subscription-hoc.tsx index 7f092c72a51..b044c1c2658 100644 --- a/src/react/hoc/subscription-hoc.tsx +++ b/src/react/hoc/subscription-hoc.tsx @@ -1,24 +1,24 @@ -import * as React from 'react'; -import type { DocumentNode } from 'graphql'; -import hoistNonReactStatics from 'hoist-non-react-statics'; +import * as React from "react"; +import type { DocumentNode } from "graphql"; +import hoistNonReactStatics from "hoist-non-react-statics"; -import { parser } from '../parser/index.js'; -import type { BaseQueryOptions } from '../types/types.js'; -import { Subscription } from '../components/index.js'; +import { parser } from "../parser/index.js"; +import type { BaseQueryOptions } from "../types/types.js"; +import { Subscription } from "../components/index.js"; import { getDisplayName, GraphQLBase, calculateVariablesFromProps, defaultMapPropsToOptions, - defaultMapPropsToSkip -} from './hoc-utils.js'; -import type { OperationOption, OptionProps, DataProps } from './types.js'; + defaultMapPropsToSkip, +} from "./hoc-utils.js"; +import type { OperationOption, OptionProps, DataProps } from "./types.js"; export function withSubscription< TProps extends TGraphQLVariables | {} = {}, TData extends object = {}, TGraphQLVariables extends object = {}, - TChildProps extends object = DataProps + TChildProps extends object = DataProps, >( document: DocumentNode, operationOptions: OperationOption< @@ -34,16 +34,16 @@ export function withSubscription< const { options = defaultMapPropsToOptions, skip = defaultMapPropsToSkip, - alias = 'Apollo', - shouldResubscribe + alias = "Apollo", + shouldResubscribe, } = operationOptions; let mapPropsToOptions = options as (props: any) => BaseQueryOptions; - if (typeof mapPropsToOptions !== 'function') + if (typeof mapPropsToOptions !== "function") mapPropsToOptions = () => options as BaseQueryOptions; let mapPropsToSkip = skip as (props: any) => boolean; - if (typeof mapPropsToSkip !== 'function') mapPropsToSkip = () => skip as any; + if (typeof mapPropsToSkip !== "function") mapPropsToSkip = () => skip as any; // allow for advanced referential equality checks let lastResultProps: TChildProps | void; @@ -69,8 +69,7 @@ export function withSubscription< componentDidUpdate(prevProps: TProps) { const resubscribe = !!( - shouldResubscribe && - shouldResubscribe(prevProps, this.props) + shouldResubscribe && shouldResubscribe(prevProps, this.props) ); if (this.state.resubscribe !== resubscribe) { this.updateResubscribe(resubscribe); @@ -99,7 +98,7 @@ export function withSubscription< if (operationOptions.withRef) { this.withRef = true; props = Object.assign({}, props, { - ref: this.setWrappedInstance + ref: this.setWrappedInstance, }); } // if we have skipped, no reason to manage any reshaping @@ -116,17 +115,14 @@ export function withSubscription< // up onto the result since it was passed as a nested prop // we massage the Query components shape here to replicate that const result = Object.assign(r, data || {}); - const name = operationOptions.name || 'data'; + const name = operationOptions.name || "data"; let childProps = { [name]: result }; if (operationOptions.props) { - const newResult: OptionProps< - TProps, - TData, - TGraphQLVariables - > = { - [name]: result, - ownProps: props as TProps - }; + const newResult: OptionProps = + { + [name]: result, + ownProps: props as TProps, + }; lastResultProps = operationOptions.props( newResult, lastResultProps diff --git a/src/react/hoc/types.ts b/src/react/hoc/types.ts index 26a24f1ef73..0d67f4c1ca0 100644 --- a/src/react/hoc/types.ts +++ b/src/react/hoc/types.ts @@ -1,5 +1,5 @@ -import type { ApolloCache, ApolloClient } from '../../core/index.js'; -import type { ApolloError } from '../../errors/index.js'; +import type { ApolloCache, ApolloClient } from "../../core/index.js"; +import type { ApolloError } from "../../errors/index.js"; import type { ApolloQueryResult, OperationVariables, @@ -8,17 +8,17 @@ import type { FetchMoreQueryOptions, SubscribeToMoreOptions, DefaultContext, -} from '../../core/index.js'; +} from "../../core/index.js"; import type { MutationFunction, BaseQueryOptions, BaseMutationOptions, - MutationResult -} from '../types/types.js'; + MutationResult, +} from "../types/types.js"; export interface QueryControls< TData = any, - TGraphQLVariables = OperationVariables + TGraphQLVariables = OperationVariables, > { error?: ApolloError; networkStatus: number; @@ -39,7 +39,7 @@ export interface QueryControls< export type DataValue< TData, - TGraphQLVariables = OperationVariables + TGraphQLVariables = OperationVariables, > = QueryControls & // data may not yet be loaded Partial; @@ -50,7 +50,7 @@ export interface DataProps { export interface MutateProps< TData = any, - TGraphQLVariables = OperationVariables + TGraphQLVariables = OperationVariables, > { mutate: MutationFunction; result: MutationResult; @@ -59,7 +59,7 @@ export interface MutateProps< export type ChildProps< TProps = {}, TData = {}, - TGraphQLVariables = OperationVariables + TGraphQLVariables = OperationVariables, > = TProps & Partial> & Partial>; @@ -67,21 +67,20 @@ export type ChildProps< export type ChildDataProps< TProps = {}, TData = {}, - TGraphQLVariables = OperationVariables + TGraphQLVariables = OperationVariables, > = TProps & DataProps; export type ChildMutateProps< TProps = {}, TData = {}, - TGraphQLVariables = OperationVariables + TGraphQLVariables = OperationVariables, > = TProps & MutateProps; export interface OptionProps< TProps = any, TData = any, - TGraphQLVariables = OperationVariables -> - extends Partial>, + TGraphQLVariables = OperationVariables, +> extends Partial>, Partial> { ownProps: TProps; } @@ -101,8 +100,7 @@ export interface OperationOption< props: TProps ) => | BaseQueryOptions - | BaseMutationOptions - ); + | BaseMutationOptions); props?: ( props: OptionProps, lastProps?: TChildProps | void diff --git a/src/react/hoc/withApollo.tsx b/src/react/hoc/withApollo.tsx index c039c9c2421..bfa21b454a4 100644 --- a/src/react/hoc/withApollo.tsx +++ b/src/react/hoc/withApollo.tsx @@ -1,30 +1,30 @@ -import { invariant } from '../../utilities/globals/index.js'; -import * as React from 'react'; -import hoistNonReactStatics from 'hoist-non-react-statics'; +import { invariant } from "../../utilities/globals/index.js"; +import * as React from "react"; +import hoistNonReactStatics from "hoist-non-react-statics"; -import { ApolloConsumer } from '../context/index.js'; -import type { OperationOption, WithApolloClient } from './types.js'; +import { ApolloConsumer } from "../context/index.js"; +import type { OperationOption, WithApolloClient } from "./types.js"; function getDisplayName

(WrappedComponent: React.ComponentType

) { - return WrappedComponent.displayName || WrappedComponent.name || 'Component'; + return WrappedComponent.displayName || WrappedComponent.name || "Component"; } export function withApollo( WrappedComponent: React.ComponentType< - WithApolloClient> + WithApolloClient> >, operationOptions: OperationOption = {} -): React.ComponentClass> { +): React.ComponentClass> { const withDisplayName = `withApollo(${getDisplayName(WrappedComponent)})`; - class WithApollo extends React.Component> { + class WithApollo extends React.Component> { static displayName = withDisplayName; static WrappedComponent = WrappedComponent; // wrapped instance private wrappedInstance: any; - constructor(props: Omit) { + constructor(props: Omit) { super(props); this.setWrappedInstance = this.setWrappedInstance.bind(this); } @@ -46,12 +46,12 @@ export function withApollo( render() { return ( - {client => { + {(client) => { const props = Object.assign({}, this.props, { client, ref: operationOptions.withRef ? this.setWrappedInstance - : undefined + : undefined, }); return ; }} diff --git a/src/react/hooks/__tests__/useApolloClient.test.tsx b/src/react/hooks/__tests__/useApolloClient.test.tsx index 3f3f9883665..920353ae283 100644 --- a/src/react/hooks/__tests__/useApolloClient.test.tsx +++ b/src/react/hooks/__tests__/useApolloClient.test.tsx @@ -1,18 +1,18 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import { InvariantError } from 'ts-invariant'; +import React from "react"; +import { render } from "@testing-library/react"; +import { InvariantError } from "ts-invariant"; -import { ApolloClient } from '../../../core'; -import { ApolloLink } from '../../../link/core'; -import { ApolloProvider } from '../../context'; -import { InMemoryCache } from '../../../cache'; -import { useApolloClient } from '../useApolloClient'; +import { ApolloClient } from "../../../core"; +import { ApolloLink } from "../../../link/core"; +import { ApolloProvider } from "../../context"; +import { InMemoryCache } from "../../../cache"; +import { useApolloClient } from "../useApolloClient"; -describe('useApolloClient Hook', () => { - it('should return a client instance from the context if available', () => { +describe("useApolloClient Hook", () => { + it("should return a client instance from the context if available", () => { const client = new ApolloClient({ cache: new InMemoryCache(), - link: ApolloLink.empty() + link: ApolloLink.empty(), }); function App() { diff --git a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx index 0799d23329f..65b551d6000 100644 --- a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx +++ b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx @@ -1,4 +1,4 @@ -import React, { Fragment, StrictMode, Suspense } from 'react'; +import React, { Fragment, StrictMode, Suspense } from "react"; import { act, render, @@ -6,11 +6,11 @@ import { renderHook, RenderHookOptions, waitFor, -} from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { ErrorBoundary, ErrorBoundaryProps } from 'react-error-boundary'; -import { expectTypeOf } from 'expect-type'; -import { GraphQLError } from 'graphql'; +} from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { ErrorBoundary, ErrorBoundaryProps } from "react-error-boundary"; +import { expectTypeOf } from "expect-type"; +import { GraphQLError } from "graphql"; import { gql, ApolloError, @@ -26,35 +26,35 @@ import { FetchMoreQueryOptions, OperationVariables, ApolloQueryResult, -} from '../../../core'; +} from "../../../core"; import { MockedResponse, MockedProvider, MockLink, MockSubscriptionLink, mockSingleLink, -} from '../../../testing'; +} from "../../../testing"; import { concatPagination, offsetLimitPagination, DeepPartial, -} from '../../../utilities'; -import { useBackgroundQuery } from '../useBackgroundQuery'; -import { useReadQuery } from '../useReadQuery'; -import { ApolloProvider } from '../../context'; -import { QUERY_REFERENCE_SYMBOL } from '../../cache/QueryReference'; -import { InMemoryCache } from '../../../cache'; +} from "../../../utilities"; +import { useBackgroundQuery } from "../useBackgroundQuery"; +import { useReadQuery } from "../useReadQuery"; +import { ApolloProvider } from "../../context"; +import { QUERY_REFERENCE_SYMBOL } from "../../cache/QueryReference"; +import { InMemoryCache } from "../../../cache"; import { FetchMoreFunction, RefetchFunction, QueryReference, -} from '../../../react'; +} from "../../../react"; import { SuspenseQueryHookFetchPolicy, SuspenseQueryHookOptions, -} from '../../types/types'; -import equal from '@wry/equality'; -import { RefetchWritePolicy } from '../../../core/watchQueryOptions'; +} from "../../types/types"; +import equal from "@wry/equality"; +import { RefetchWritePolicy } from "../../../core/watchQueryOptions"; function renderIntegrationTest({ client, @@ -72,7 +72,7 @@ function renderIntegrationTest({ const mocks = [ { request: { query }, - result: { data: { foo: { bar: 'hello' } } }, + result: { data: { foo: { bar: "hello" } } }, }, ]; const _client = @@ -166,7 +166,7 @@ function useVariablesIntegrationTestCase() { } } `; - const CHARACTERS = ['Spider-Man', 'Black Widow', 'Iron Man', 'Hulk']; + const CHARACTERS = ["Spider-Man", "Black Widow", "Iron Man", "Hulk"]; let mocks = [...CHARACTERS].map((name, index) => ({ request: { query, variables: { id: String(index + 1) } }, result: { data: { character: { id: String(index + 1), name } } }, @@ -285,7 +285,7 @@ function renderVariablesIntegrationTest({

{todo.name} - {todo.completed && ' (completed)'} + {todo.completed && " (completed)"}
); @@ -1177,14 +1155,14 @@ describe('useBackgroundQuery', () => { render(); - expect(screen.getByText('Loading')).toBeInTheDocument(); + expect(screen.getByText("Loading")).toBeInTheDocument(); - expect(await screen.findByTestId('todo')).toBeInTheDocument(); + expect(await screen.findByTestId("todo")).toBeInTheDocument(); - const todo = screen.getByTestId('todo'); - const button = screen.getByText('Refresh'); + const todo = screen.getByTestId("todo"); + const button = screen.getByText("Refresh"); - expect(todo).toHaveTextContent('Clean room'); + expect(todo).toHaveTextContent("Clean room"); await act(() => user.click(button)); @@ -1195,18 +1173,18 @@ describe('useBackgroundQuery', () => { // Here we should not see the suspense fallback while the component suspends // until the todo is finished loading. Seeing the suspense fallback is an // indication that we are suspending the component too late in the process. - expect(screen.queryByText('Loading')).not.toBeInTheDocument(); + expect(screen.queryByText("Loading")).not.toBeInTheDocument(); // We can ensure this works with isPending from useTransition in the process - expect(todo).toHaveAttribute('aria-busy', 'true'); + expect(todo).toHaveAttribute("aria-busy", "true"); // Ensure we are showing the stale UI until the new todo has loaded - expect(todo).toHaveTextContent('Clean room'); + expect(todo).toHaveTextContent("Clean room"); // Eventually we should see the updated todo content once its done // suspending. await waitFor(() => { - expect(todo).toHaveTextContent('Take out trash (completed)'); + expect(todo).toHaveTextContent("Take out trash (completed)"); }); }); @@ -1238,9 +1216,9 @@ describe('useBackgroundQuery', () => { query, data: { greeting: { - __typename: 'Greeting', - message: 'Hello cached', - recipient: { __typename: 'Person', name: 'Cached Alice' }, + __typename: "Greeting", + message: "Hello cached", + recipient: { __typename: "Person", name: "Cached Alice" }, }, }, }); @@ -1265,7 +1243,7 @@ describe('useBackgroundQuery', () => { function Parent() { const [queryRef] = useBackgroundQuery(query, { - fetchPolicy: 'cache-and-network', + fetchPolicy: "cache-and-network", }); return ; } @@ -1280,7 +1258,7 @@ describe('useBackgroundQuery', () => {
Message: {greeting.message}
Recipient: {greeting.recipient.name}
Network status: {networkStatus}
-
Error: {error ? error.message : 'none'}
+
Error: {error ? error.message : "none"}
); } @@ -1288,20 +1266,20 @@ describe('useBackgroundQuery', () => { render(); expect(screen.getByText(/Message/i)).toHaveTextContent( - 'Message: Hello cached' + "Message: Hello cached" ); expect(screen.getByText(/Recipient/i)).toHaveTextContent( - 'Recipient: Cached Alice' + "Recipient: Cached Alice" ); expect(screen.getByText(/Network status/i)).toHaveTextContent( - 'Network status: 1' // loading + "Network status: 1" // loading ); - expect(screen.getByText(/Error/i)).toHaveTextContent('none'); + expect(screen.getByText(/Error/i)).toHaveTextContent("none"); link.simulateResult({ result: { data: { - greeting: { __typename: 'Greeting', message: 'Hello world' }, + greeting: { __typename: "Greeting", message: "Hello world" }, }, hasNext: true, }, @@ -1309,26 +1287,26 @@ describe('useBackgroundQuery', () => { await waitFor(() => { expect(screen.getByText(/Message/i)).toHaveTextContent( - 'Message: Hello world' + "Message: Hello world" ); }); expect(screen.getByText(/Recipient/i)).toHaveTextContent( - 'Recipient: Cached Alice' + "Recipient: Cached Alice" ); expect(screen.getByText(/Network status/i)).toHaveTextContent( - 'Network status: 7' // ready + "Network status: 7" // ready ); - expect(screen.getByText(/Error/i)).toHaveTextContent('none'); + expect(screen.getByText(/Error/i)).toHaveTextContent("none"); link.simulateResult({ result: { incremental: [ { data: { - recipient: { name: 'Alice', __typename: 'Person' }, - __typename: 'Greeting', + recipient: { name: "Alice", __typename: "Person" }, + __typename: "Greeting", }, - path: ['greeting'], + path: ["greeting"], }, ], hasNext: false, @@ -1337,72 +1315,72 @@ describe('useBackgroundQuery', () => { await waitFor(() => { expect(screen.getByText(/Recipient/i)).toHaveTextContent( - 'Recipient: Alice' + "Recipient: Alice" ); }); expect(screen.getByText(/Message/i)).toHaveTextContent( - 'Message: Hello world' + "Message: Hello world" ); expect(screen.getByText(/Network status/i)).toHaveTextContent( - 'Network status: 7' // ready + "Network status: 7" // ready ); - expect(screen.getByText(/Error/i)).toHaveTextContent('none'); + expect(screen.getByText(/Error/i)).toHaveTextContent("none"); expect(renders).toBe(3); expect(suspenseCount).toBe(0); }); }); - it('reacts to cache updates', async () => { + it("reacts to cache updates", async () => { const { renders, client, query } = renderIntegrationTest(); expect(renders.suspenseCount).toBe(1); - expect(screen.getByText('loading')).toBeInTheDocument(); + expect(screen.getByText("loading")).toBeInTheDocument(); // the parent component re-renders when promise fulfilled - expect(await screen.findByText('hello')).toBeInTheDocument(); + expect(await screen.findByText("hello")).toBeInTheDocument(); expect(renders.count).toBe(1); client.writeQuery({ query, - data: { foo: { bar: 'baz' } }, + data: { foo: { bar: "baz" } }, }); // the parent component re-renders when promise fulfilled - expect(await screen.findByText('baz')).toBeInTheDocument(); + expect(await screen.findByText("baz")).toBeInTheDocument(); expect(renders.count).toBe(2); expect(renders.suspenseCount).toBe(1); client.writeQuery({ query, - data: { foo: { bar: 'bat' } }, + data: { foo: { bar: "bat" } }, }); - expect(await screen.findByText('bat')).toBeInTheDocument(); + expect(await screen.findByText("bat")).toBeInTheDocument(); expect(renders.suspenseCount).toBe(1); }); - it('reacts to variables updates', async () => { + it("reacts to variables updates", async () => { const { renders, rerender } = renderVariablesIntegrationTest({ - variables: { id: '1' }, + variables: { id: "1" }, }); expect(renders.suspenseCount).toBe(1); - expect(screen.getByText('loading')).toBeInTheDocument(); + expect(screen.getByText("loading")).toBeInTheDocument(); - expect(await screen.findByText('1 - Spider-Man')).toBeInTheDocument(); + expect(await screen.findByText("1 - Spider-Man")).toBeInTheDocument(); - rerender({ variables: { id: '2' } }); + rerender({ variables: { id: "2" } }); expect(renders.suspenseCount).toBe(2); - expect(screen.getByText('loading')).toBeInTheDocument(); + expect(screen.getByText("loading")).toBeInTheDocument(); - expect(await screen.findByText('2 - Black Widow')).toBeInTheDocument(); + expect(await screen.findByText("2 - Black Widow")).toBeInTheDocument(); }); - it('does not suspend when `skip` is true', async () => { + it("does not suspend when `skip` is true", async () => { interface Data { greeting: string; } @@ -1416,7 +1394,7 @@ describe('useBackgroundQuery', () => { const mocks = [ { request: { query }, - result: { data: { greeting: 'Hello' } }, + result: { data: { greeting: "Hello" } }, }, ]; @@ -1447,7 +1425,7 @@ describe('useBackgroundQuery', () => { const { data } = useReadQuery(queryRef); return ( -
{data ? data.greeting : 'Unknown'}
+
{data ? data.greeting : "Unknown"}
); } @@ -1461,10 +1439,10 @@ describe('useBackgroundQuery', () => { render(); - expect(screen.getByTestId('greeting')).toHaveTextContent('Unknown'); + expect(screen.getByTestId("greeting")).toHaveTextContent("Unknown"); }); - it('suspends when `skip` becomes `false` after it was `true`', async () => { + it("suspends when `skip` becomes `false` after it was `true`", async () => { interface Data { greeting: string; } @@ -1480,7 +1458,7 @@ describe('useBackgroundQuery', () => { const mocks = [ { request: { query }, - result: { data: { greeting: 'Hello' } }, + result: { data: { greeting: "Hello" } }, }, ]; @@ -1515,7 +1493,7 @@ describe('useBackgroundQuery', () => { const { data } = useReadQuery(queryRef); return ( -
{data ? data.greeting : 'Unknown'}
+
{data ? data.greeting : "Unknown"}
); } @@ -1529,20 +1507,20 @@ describe('useBackgroundQuery', () => { render(); - const greeting = screen.getByTestId('greeting'); + const greeting = screen.getByTestId("greeting"); - expect(greeting).toHaveTextContent('Unknown'); + expect(greeting).toHaveTextContent("Unknown"); - await act(() => user.click(screen.getByText('Run query'))); + await act(() => user.click(screen.getByText("Run query"))); - expect(screen.getByText('Loading...')).toBeInTheDocument(); + expect(screen.getByText("Loading...")).toBeInTheDocument(); await waitFor(() => { - expect(greeting).toHaveTextContent('Hello'); + expect(greeting).toHaveTextContent("Hello"); }); }); - it('renders skip result, does not suspend, and maintains `data` when `skip` becomes `true` after it was `false`', async () => { + it("renders skip result, does not suspend, and maintains `data` when `skip` becomes `true` after it was `false`", async () => { interface Data { greeting: string; } @@ -1558,7 +1536,7 @@ describe('useBackgroundQuery', () => { const mocks = [ { request: { query }, - result: { data: { greeting: 'Hello' } }, + result: { data: { greeting: "Hello" } }, }, ]; @@ -1593,7 +1571,7 @@ describe('useBackgroundQuery', () => { const { data } = useReadQuery(queryRef); return ( -
{data ? data.greeting : 'Unknown'}
+
{data ? data.greeting : "Unknown"}
); } @@ -1607,18 +1585,18 @@ describe('useBackgroundQuery', () => { render(); - expect(screen.getByText('Loading...')).toBeInTheDocument(); + expect(screen.getByText("Loading...")).toBeInTheDocument(); await waitFor(() => { - expect(screen.getByTestId('greeting')).toHaveTextContent('Hello'); + expect(screen.getByTestId("greeting")).toHaveTextContent("Hello"); }); - await act(() => user.click(screen.getByText('Toggle skip'))); + await act(() => user.click(screen.getByText("Toggle skip"))); - expect(screen.getByTestId('greeting')).toHaveTextContent('Hello'); + expect(screen.getByTestId("greeting")).toHaveTextContent("Hello"); }); - it('does not make network requests when `skip` is `true`', async () => { + it("does not make network requests when `skip` is `true`", async () => { interface Data { greeting: string; } @@ -1634,7 +1612,7 @@ describe('useBackgroundQuery', () => { const mocks = [ { request: { query }, - result: { data: { greeting: 'Hello' } }, + result: { data: { greeting: "Hello" } }, }, ]; @@ -1649,7 +1627,7 @@ describe('useBackgroundQuery', () => { ); if (!mock) { - throw new Error('Could not find mock for operation'); + throw new Error("Could not find mock for operation"); } observer.next(mock.result); @@ -1688,7 +1666,7 @@ describe('useBackgroundQuery', () => { const { data } = useReadQuery(queryRef); return ( -
{data ? data.greeting : 'Unknown'}
+
{data ? data.greeting : "Unknown"}
); } @@ -1705,21 +1683,21 @@ describe('useBackgroundQuery', () => { expect(fetchCount).toBe(0); // Toggle skip to `false` - await act(() => user.click(screen.getByText('Toggle skip'))); + await act(() => user.click(screen.getByText("Toggle skip"))); expect(fetchCount).toBe(1); await waitFor(() => { - expect(screen.getByTestId('greeting')).toHaveTextContent('Hello'); + expect(screen.getByTestId("greeting")).toHaveTextContent("Hello"); }); // Toggle skip to `true` - await act(() => user.click(screen.getByText('Toggle skip'))); + await act(() => user.click(screen.getByText("Toggle skip"))); expect(fetchCount).toBe(1); }); - it('`skip` result is referentially stable', async () => { + it("`skip` result is referentially stable", async () => { interface Data { greeting: string; } @@ -1743,7 +1721,7 @@ describe('useBackgroundQuery', () => { const mocks = [ { request: { query }, - result: { data: { greeting: 'Hello' } }, + result: { data: { greeting: "Hello" } }, }, ]; @@ -1780,7 +1758,7 @@ describe('useBackgroundQuery', () => { result.current = data; return ( -
{data ? data.greeting : 'Unknown'}
+
{data ? data.greeting : "Unknown"}
); } @@ -1801,12 +1779,12 @@ describe('useBackgroundQuery', () => { expect(result.current).toBe(skipResult); // Toggle skip to `false` - await act(() => user.click(screen.getByText('Toggle skip'))); + await act(() => user.click(screen.getByText("Toggle skip"))); - expect(screen.getByText('Loading...')).toBeInTheDocument(); + expect(screen.getByText("Loading...")).toBeInTheDocument(); await waitFor(() => { - expect(screen.getByTestId('greeting')).toHaveTextContent('Hello'); + expect(screen.getByTestId("greeting")).toHaveTextContent("Hello"); }); const fetchedResult = result.current; @@ -1816,7 +1794,7 @@ describe('useBackgroundQuery', () => { expect(result.current).toBe(fetchedResult); }); - it('`skip` option works with `startTransition`', async () => { + it("`skip` option works with `startTransition`", async () => { interface Data { greeting: string; } @@ -1832,7 +1810,7 @@ describe('useBackgroundQuery', () => { const mocks = [ { request: { query }, - result: { data: { greeting: 'Hello' } }, + result: { data: { greeting: "Hello" } }, delay: 10, }, ]; @@ -1878,7 +1856,7 @@ describe('useBackgroundQuery', () => { const { data } = useReadQuery(queryRef); return ( -
{data ? data.greeting : 'Unknown'}
+
{data ? data.greeting : "Unknown"}
); } @@ -1892,21 +1870,21 @@ describe('useBackgroundQuery', () => { render(); - const button = screen.getByText('Toggle skip'); + const button = screen.getByText("Toggle skip"); // Toggle skip to `false` await act(() => user.click(button)); - expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); + expect(screen.queryByText("Loading...")).not.toBeInTheDocument(); expect(button).toBeDisabled(); - expect(screen.getByTestId('greeting')).toHaveTextContent('Unknown'); + expect(screen.getByTestId("greeting")).toHaveTextContent("Unknown"); await waitFor(() => { - expect(screen.getByTestId('greeting')).toHaveTextContent('Hello'); + expect(screen.getByTestId("greeting")).toHaveTextContent("Hello"); }); }); - it('applies `errorPolicy` on next fetch when it changes between renders', async () => { + it("applies `errorPolicy` on next fetch when it changes between renders", async () => { interface Data { greeting: string; } @@ -1922,12 +1900,12 @@ describe('useBackgroundQuery', () => { const mocks = [ { request: { query }, - result: { data: { greeting: 'Hello' } }, + result: { data: { greeting: "Hello" } }, }, { request: { query }, result: { - errors: [new GraphQLError('oops')], + errors: [new GraphQLError("oops")], }, }, ]; @@ -1942,14 +1920,14 @@ describe('useBackgroundQuery', () => { } function Parent() { - const [errorPolicy, setErrorPolicy] = React.useState('none'); + const [errorPolicy, setErrorPolicy] = React.useState("none"); const [queryRef, { refetch }] = useBackgroundQuery(query, { errorPolicy, }); return ( <> - @@ -1984,17 +1962,17 @@ describe('useBackgroundQuery', () => { render(); - expect(await screen.findByTestId('greeting')).toHaveTextContent('Hello'); + expect(await screen.findByTestId("greeting")).toHaveTextContent("Hello"); - await act(() => user.click(screen.getByText('Change error policy'))); - await act(() => user.click(screen.getByText('Refetch greeting'))); + await act(() => user.click(screen.getByText("Change error policy"))); + await act(() => user.click(screen.getByText("Refetch greeting"))); // Ensure we aren't rendering the error boundary and instead rendering the // error message in the Greeting component. - expect(await screen.findByTestId('error')).toHaveTextContent('oops'); + expect(await screen.findByTestId("error")).toHaveTextContent("oops"); }); - it('applies `context` on next fetch when it changes between renders', async () => { + it("applies `context` on next fetch when it changes between renders", async () => { interface Data { context: Record; } @@ -2025,14 +2003,14 @@ describe('useBackgroundQuery', () => { } function Parent() { - const [phase, setPhase] = React.useState('initial'); + const [phase, setPhase] = React.useState("initial"); const [queryRef, { refetch }] = useBackgroundQuery(query, { context: { phase }, }); return ( <> - + }> @@ -2057,18 +2035,18 @@ describe('useBackgroundQuery', () => { render(); - expect(await screen.findByTestId('context')).toHaveTextContent('initial'); + expect(await screen.findByTestId("context")).toHaveTextContent("initial"); - await act(() => user.click(screen.getByText('Update context'))); - await act(() => user.click(screen.getByText('Refetch'))); + await act(() => user.click(screen.getByText("Update context"))); + await act(() => user.click(screen.getByText("Refetch"))); - expect(await screen.findByTestId('context')).toHaveTextContent('rerender'); + expect(await screen.findByTestId("context")).toHaveTextContent("rerender"); }); // NOTE: We only test the `false` -> `true` path here. If the option changes // from `true` -> `false`, the data has already been canonized, so it has no // effect on the output. - it('returns canonical results immediately when `canonizeResults` changes from `false` to `true` between renders', async () => { + it("returns canonical results immediately when `canonizeResults` changes from `false` to `true` between renders", async () => { interface Result { __typename: string; value: number; @@ -2095,12 +2073,12 @@ describe('useBackgroundQuery', () => { `; const results: Result[] = [ - { __typename: 'Result', value: 0 }, - { __typename: 'Result', value: 1 }, - { __typename: 'Result', value: 1 }, - { __typename: 'Result', value: 2 }, - { __typename: 'Result', value: 3 }, - { __typename: 'Result', value: 5 }, + { __typename: "Result", value: 0 }, + { __typename: "Result", value: 1 }, + { __typename: "Result", value: 1 }, + { __typename: "Result", value: 2 }, + { __typename: "Result", value: 3 }, + { __typename: "Result", value: 5 }, ]; const user = userEvent.setup(); @@ -2178,12 +2156,12 @@ describe('useBackgroundQuery', () => { verifyCanonicalResults(result.current!, false); - await act(() => user.click(screen.getByText('Canonize results'))); + await act(() => user.click(screen.getByText("Canonize results"))); verifyCanonicalResults(result.current!, true); }); - it('applies changed `refetchWritePolicy` to next fetch when changing between renders', async () => { + it("applies changed `refetchWritePolicy` to next fetch when changing between renders", async () => { interface Data { primes: number[]; } @@ -2242,7 +2220,7 @@ describe('useBackgroundQuery', () => { function Parent() { const [refetchWritePolicy, setRefetchWritePolicy] = - React.useState('merge'); + React.useState("merge"); const [queryRef, { refetch }] = useBackgroundQuery(query, { refetchWritePolicy, @@ -2251,7 +2229,7 @@ describe('useBackgroundQuery', () => { return ( <> - @@ -2558,31 +2536,31 @@ describe('useBackgroundQuery', () => { render(); - const character = await screen.findByTestId('character'); + const character = await screen.findByTestId("character"); - expect(character).toHaveTextContent('Doctor Strangecache'); + expect(character).toHaveTextContent("Doctor Strangecache"); - await act(() => user.click(screen.getByText('Change fetch policy'))); - await act(() => user.click(screen.getByText('Refetch'))); + await act(() => user.click(screen.getByText("Change fetch policy"))); + await act(() => user.click(screen.getByText("Refetch"))); await waitFor(() => { - expect(character).toHaveTextContent('Doctor Strange'); + expect(character).toHaveTextContent("Doctor Strange"); }); // Because we switched to a `no-cache` fetch policy, we should not see the // newly fetched data in the cache after the fetch occured. expect(cache.readQuery({ query })).toEqual({ character: { - __typename: 'Character', - id: '1', - name: 'Doctor Strangecache', + __typename: "Character", + id: "1", + name: "Doctor Strangecache", }, }); }); - it('properly handles changing options along with changing `variables`', async () => { + it("properly handles changing options along with changing `variables`", async () => { interface Data { character: { - __typename: 'Character'; + __typename: "Character"; id: string; name: string; }; @@ -2601,20 +2579,20 @@ describe('useBackgroundQuery', () => { const mocks = [ { - request: { query, variables: { id: '1' } }, + request: { query, variables: { id: "1" } }, result: { - errors: [new GraphQLError('oops')], + errors: [new GraphQLError("oops")], }, delay: 10, }, { - request: { query, variables: { id: '2' } }, + request: { query, variables: { id: "2" } }, result: { data: { character: { - __typename: 'Character', - id: '2', - name: 'Hulk', + __typename: "Character", + id: "2", + name: "Hulk", }, }, }, @@ -2627,13 +2605,13 @@ describe('useBackgroundQuery', () => { cache.writeQuery({ query, variables: { - id: '1', + id: "1", }, data: { character: { - __typename: 'Character', - id: '1', - name: 'Doctor Strangecache', + __typename: "Character", + id: "1", + name: "Doctor Strangecache", }, }, }); @@ -2648,17 +2626,17 @@ describe('useBackgroundQuery', () => { } function Parent() { - const [id, setId] = React.useState('1'); + const [id, setId] = React.useState("1"); const [queryRef, { refetch }] = useBackgroundQuery(query, { - errorPolicy: id === '1' ? 'all' : 'none', + errorPolicy: id === "1" ? "all" : "none", variables: { id }, }); return ( <> - - + + Error boundary
} @@ -2691,41 +2669,41 @@ describe('useBackgroundQuery', () => { render(); - const character = await screen.findByTestId('character'); + const character = await screen.findByTestId("character"); - expect(character).toHaveTextContent('Doctor Strangecache'); + expect(character).toHaveTextContent("Doctor Strangecache"); - await act(() => user.click(screen.getByText('Get second character'))); + await act(() => user.click(screen.getByText("Get second character"))); await waitFor(() => { - expect(character).toHaveTextContent('Hulk'); + expect(character).toHaveTextContent("Hulk"); }); - await act(() => user.click(screen.getByText('Get first character'))); + await act(() => user.click(screen.getByText("Get first character"))); await waitFor(() => { - expect(character).toHaveTextContent('Doctor Strangecache'); + expect(character).toHaveTextContent("Doctor Strangecache"); }); - await act(() => user.click(screen.getByText('Refetch'))); + await act(() => user.click(screen.getByText("Refetch"))); // Ensure we render the inline error instead of the error boundary, which // tells us the error policy was properly applied. - expect(await screen.findByTestId('error')).toHaveTextContent('oops'); + expect(await screen.findByTestId("error")).toHaveTextContent("oops"); }); - describe('refetch', () => { - it('re-suspends when calling `refetch`', async () => { + describe("refetch", () => { + it("re-suspends when calling `refetch`", async () => { const { renders } = renderVariablesIntegrationTest({ - variables: { id: '1' }, + variables: { id: "1" }, }); expect(renders.suspenseCount).toBe(1); - expect(screen.getByText('loading')).toBeInTheDocument(); + expect(screen.getByText("loading")).toBeInTheDocument(); - expect(await screen.findByText('1 - Spider-Man')).toBeInTheDocument(); + expect(await screen.findByText("1 - Spider-Man")).toBeInTheDocument(); - const button = screen.getByText('Refetch'); + const button = screen.getByText("Refetch"); const user = userEvent.setup(); await act(() => user.click(button)); @@ -2734,10 +2712,10 @@ describe('useBackgroundQuery', () => { expect(renders.count).toBe(2); expect( - await screen.findByText('1 - Spider-Man (updated)') + await screen.findByText("1 - Spider-Man (updated)") ).toBeInTheDocument(); }); - it('re-suspends when calling `refetch` with new variables', async () => { + it("re-suspends when calling `refetch` with new variables", async () => { interface QueryData { character: { id: string; @@ -2759,39 +2737,39 @@ describe('useBackgroundQuery', () => { const mocks = [ { - request: { query, variables: { id: '1' } }, + request: { query, variables: { id: "1" } }, result: { - data: { character: { id: '1', name: 'Captain Marvel' } }, + data: { character: { id: "1", name: "Captain Marvel" } }, }, }, { - request: { query, variables: { id: '2' } }, + request: { query, variables: { id: "2" } }, result: { - data: { character: { id: '2', name: 'Captain America' } }, + data: { character: { id: "2", name: "Captain America" } }, }, }, ]; const { renders } = renderVariablesIntegrationTest({ - variables: { id: '1' }, + variables: { id: "1" }, mocks, }); expect(renders.suspenseCount).toBe(1); - expect(screen.getByText('loading')).toBeInTheDocument(); + expect(screen.getByText("loading")).toBeInTheDocument(); - expect(await screen.findByText('1 - Captain Marvel')).toBeInTheDocument(); + expect(await screen.findByText("1 - Captain Marvel")).toBeInTheDocument(); const newVariablesRefetchButton = screen.getByText( - 'Set variables to id: 2' + "Set variables to id: 2" ); - const refetchButton = screen.getByText('Refetch'); + const refetchButton = screen.getByText("Refetch"); const user = userEvent.setup(); await act(() => user.click(newVariablesRefetchButton)); await act(() => user.click(refetchButton)); expect( - await screen.findByText('2 - Captain America') + await screen.findByText("2 - Captain America") ).toBeInTheDocument(); // parent component re-suspends @@ -2817,17 +2795,17 @@ describe('useBackgroundQuery', () => { }, ]); }); - it('re-suspends multiple times when calling `refetch` multiple times', async () => { + it("re-suspends multiple times when calling `refetch` multiple times", async () => { const { renders } = renderVariablesIntegrationTest({ - variables: { id: '1' }, + variables: { id: "1" }, }); expect(renders.suspenseCount).toBe(1); - expect(screen.getByText('loading')).toBeInTheDocument(); + expect(screen.getByText("loading")).toBeInTheDocument(); - expect(await screen.findByText('1 - Spider-Man')).toBeInTheDocument(); + expect(await screen.findByText("1 - Spider-Man")).toBeInTheDocument(); - const button = screen.getByText('Refetch'); + const button = screen.getByText("Refetch"); const user = userEvent.setup(); await act(() => user.click(button)); @@ -2836,7 +2814,7 @@ describe('useBackgroundQuery', () => { expect(renders.count).toBe(2); expect( - await screen.findByText('1 - Spider-Man (updated)') + await screen.findByText("1 - Spider-Man (updated)") ).toBeInTheDocument(); await act(() => user.click(button)); @@ -2846,11 +2824,11 @@ describe('useBackgroundQuery', () => { expect(renders.count).toBe(3); expect( - await screen.findByText('1 - Spider-Man (updated again)') + await screen.findByText("1 - Spider-Man (updated again)") ).toBeInTheDocument(); }); - it('throws errors when errors are returned after calling `refetch`', async () => { - const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); + it("throws errors when errors are returned after calling `refetch`", async () => { + const consoleSpy = jest.spyOn(console, "error").mockImplementation(); interface QueryData { character: { id: string; @@ -2871,29 +2849,29 @@ describe('useBackgroundQuery', () => { `; const mocks = [ { - request: { query, variables: { id: '1' } }, + request: { query, variables: { id: "1" } }, result: { - data: { character: { id: '1', name: 'Captain Marvel' } }, + data: { character: { id: "1", name: "Captain Marvel" } }, }, }, { - request: { query, variables: { id: '1' } }, + request: { query, variables: { id: "1" } }, result: { - errors: [new GraphQLError('Something went wrong')], + errors: [new GraphQLError("Something went wrong")], }, }, ]; const { renders } = renderVariablesIntegrationTest({ - variables: { id: '1' }, + variables: { id: "1" }, mocks, }); expect(renders.suspenseCount).toBe(1); - expect(screen.getByText('loading')).toBeInTheDocument(); + expect(screen.getByText("loading")).toBeInTheDocument(); - expect(await screen.findByText('1 - Captain Marvel')).toBeInTheDocument(); + expect(await screen.findByText("1 - Captain Marvel")).toBeInTheDocument(); - const button = screen.getByText('Refetch'); + const button = screen.getByText("Refetch"); const user = userEvent.setup(); await act(() => user.click(button)); @@ -2903,7 +2881,7 @@ describe('useBackgroundQuery', () => { expect(renders.errors).toEqual([ new ApolloError({ - graphQLErrors: [new GraphQLError('Something went wrong')], + graphQLErrors: [new GraphQLError("Something went wrong")], }), ]); @@ -2930,28 +2908,28 @@ describe('useBackgroundQuery', () => { `; const mocks = [ { - request: { query, variables: { id: '1' } }, + request: { query, variables: { id: "1" } }, result: { - data: { character: { id: '1', name: 'Captain Marvel' } }, + data: { character: { id: "1", name: "Captain Marvel" } }, }, }, { - request: { query, variables: { id: '1' } }, + request: { query, variables: { id: "1" } }, result: { - errors: [new GraphQLError('Something went wrong')], + errors: [new GraphQLError("Something went wrong")], }, }, ]; const { renders } = renderVariablesIntegrationTest({ - variables: { id: '1' }, - errorPolicy: 'ignore', + variables: { id: "1" }, + errorPolicy: "ignore", mocks, }); - expect(await screen.findByText('1 - Captain Marvel')).toBeInTheDocument(); + expect(await screen.findByText("1 - Captain Marvel")).toBeInTheDocument(); - const button = screen.getByText('Refetch'); + const button = screen.getByText("Refetch"); const user = userEvent.setup(); await act(() => user.click(button)); @@ -2979,28 +2957,28 @@ describe('useBackgroundQuery', () => { `; const mocks = [ { - request: { query, variables: { id: '1' } }, + request: { query, variables: { id: "1" } }, result: { - data: { character: { id: '1', name: 'Captain Marvel' } }, + data: { character: { id: "1", name: "Captain Marvel" } }, }, }, { - request: { query, variables: { id: '1' } }, + request: { query, variables: { id: "1" } }, result: { - errors: [new GraphQLError('Something went wrong')], + errors: [new GraphQLError("Something went wrong")], }, }, ]; const { renders } = renderVariablesIntegrationTest({ - variables: { id: '1' }, - errorPolicy: 'all', + variables: { id: "1" }, + errorPolicy: "all", mocks, }); - expect(await screen.findByText('1 - Captain Marvel')).toBeInTheDocument(); + expect(await screen.findByText("1 - Captain Marvel")).toBeInTheDocument(); - const button = screen.getByText('Refetch'); + const button = screen.getByText("Refetch"); const user = userEvent.setup(); await act(() => user.click(button)); @@ -3008,7 +2986,7 @@ describe('useBackgroundQuery', () => { expect(renders.errors).toEqual([]); expect( - await screen.findByText('Something went wrong') + await screen.findByText("Something went wrong") ).toBeInTheDocument(); }); it('handles partial data results after calling `refetch` when errorPolicy is set to "all"', async () => { @@ -3032,29 +3010,29 @@ describe('useBackgroundQuery', () => { `; const mocks = [ { - request: { query, variables: { id: '1' } }, + request: { query, variables: { id: "1" } }, result: { - data: { character: { id: '1', name: 'Captain Marvel' } }, + data: { character: { id: "1", name: "Captain Marvel" } }, }, }, { - request: { query, variables: { id: '1' } }, + request: { query, variables: { id: "1" } }, result: { - data: { character: { id: '1', name: null } }, - errors: [new GraphQLError('Something went wrong')], + data: { character: { id: "1", name: null } }, + errors: [new GraphQLError("Something went wrong")], }, }, ]; const { renders } = renderVariablesIntegrationTest({ - variables: { id: '1' }, - errorPolicy: 'all', + variables: { id: "1" }, + errorPolicy: "all", mocks, }); - expect(await screen.findByText('1 - Captain Marvel')).toBeInTheDocument(); + expect(await screen.findByText("1 - Captain Marvel")).toBeInTheDocument(); - const button = screen.getByText('Refetch'); + const button = screen.getByText("Refetch"); const user = userEvent.setup(); await act(() => user.click(button)); @@ -3062,11 +3040,11 @@ describe('useBackgroundQuery', () => { expect(renders.errors).toEqual([]); expect( - await screen.findByText('Something went wrong') + await screen.findByText("Something went wrong") ).toBeInTheDocument(); const expectedError = new ApolloError({ - graphQLErrors: [new GraphQLError('Something went wrong')], + graphQLErrors: [new GraphQLError("Something went wrong")], }); expect(renders.frames).toMatchObject([ @@ -3082,7 +3060,7 @@ describe('useBackgroundQuery', () => { }, ]); }); - it('`refetch` works with startTransition to allow React to show stale UI until finished suspending', async () => { + it("`refetch` works with startTransition to allow React to show stale UI until finished suspending", async () => { type Variables = { id: string; }; @@ -3108,16 +3086,16 @@ describe('useBackgroundQuery', () => { const mocks: MockedResponse[] = [ { - request: { query, variables: { id: '1' } }, + request: { query, variables: { id: "1" } }, result: { - data: { todo: { id: '1', name: 'Clean room', completed: false } }, + data: { todo: { id: "1", name: "Clean room", completed: false } }, }, delay: 10, }, { - request: { query, variables: { id: '1' } }, + request: { query, variables: { id: "1" } }, result: { - data: { todo: { id: '1', name: 'Clean room', completed: true } }, + data: { todo: { id: "1", name: "Clean room", completed: true } }, }, delay: 10, }, @@ -3143,7 +3121,7 @@ describe('useBackgroundQuery', () => { } function Parent() { - const [id, setId] = React.useState('1'); + const [id, setId] = React.useState("1"); const [queryRef, { refetch }] = useBackgroundQuery(query, { variables: { id }, }); @@ -3175,7 +3153,7 @@ describe('useBackgroundQuery', () => {
{todo.name} - {todo.completed && ' (completed)'} + {todo.completed && " (completed)"}
); @@ -3183,14 +3161,14 @@ describe('useBackgroundQuery', () => { render(); - expect(screen.getByText('Loading')).toBeInTheDocument(); + expect(screen.getByText("Loading")).toBeInTheDocument(); - expect(await screen.findByTestId('todo')).toBeInTheDocument(); + expect(await screen.findByTestId("todo")).toBeInTheDocument(); - const todo = screen.getByTestId('todo'); - const button = screen.getByText('Refresh'); + const todo = screen.getByTestId("todo"); + const button = screen.getByText("Refresh"); - expect(todo).toHaveTextContent('Clean room'); + expect(todo).toHaveTextContent("Clean room"); await act(() => user.click(button)); @@ -3201,40 +3179,40 @@ describe('useBackgroundQuery', () => { // Here we should not see the suspense fallback while the component suspends // until the todo is finished loading. Seeing the suspense fallback is an // indication that we are suspending the component too late in the process. - expect(screen.queryByText('Loading')).not.toBeInTheDocument(); + expect(screen.queryByText("Loading")).not.toBeInTheDocument(); // We can ensure this works with isPending from useTransition in the process - expect(todo).toHaveAttribute('aria-busy', 'true'); + expect(todo).toHaveAttribute("aria-busy", "true"); // Ensure we are showing the stale UI until the new todo has loaded - expect(todo).toHaveTextContent('Clean room'); + expect(todo).toHaveTextContent("Clean room"); // Eventually we should see the updated todo content once its done // suspending. await waitFor(() => { - expect(todo).toHaveTextContent('Clean room (completed)'); + expect(todo).toHaveTextContent("Clean room (completed)"); }); }); }); - describe('fetchMore', () => { + describe("fetchMore", () => { function getItemTexts() { return screen.getAllByTestId(/letter/).map( // eslint-disable-next-line testing-library/no-node-access (li) => li.firstChild!.textContent ); } - it('re-suspends when calling `fetchMore` with different variables', async () => { + it("re-suspends when calling `fetchMore` with different variables", async () => { const { renders } = renderPaginatedIntegrationTest(); expect(renders.suspenseCount).toBe(1); - expect(screen.getByText('loading')).toBeInTheDocument(); + expect(screen.getByText("loading")).toBeInTheDocument(); const items = await screen.findAllByTestId(/letter/i); expect(items).toHaveLength(2); - expect(getItemTexts()).toStrictEqual(['A', 'B']); + expect(getItemTexts()).toStrictEqual(["A", "B"]); - const button = screen.getByText('Fetch more'); + const button = screen.getByText("Fetch more"); const user = userEvent.setup(); await act(() => user.click(button)); @@ -3244,22 +3222,22 @@ describe('useBackgroundQuery', () => { expect(renders.count).toBe(2); }); - expect(getItemTexts()).toStrictEqual(['C', 'D']); + expect(getItemTexts()).toStrictEqual(["C", "D"]); }); - it('properly uses `updateQuery` when calling `fetchMore`', async () => { + it("properly uses `updateQuery` when calling `fetchMore`", async () => { const { renders } = renderPaginatedIntegrationTest({ updateQuery: true, }); expect(renders.suspenseCount).toBe(1); - expect(screen.getByText('loading')).toBeInTheDocument(); + expect(screen.getByText("loading")).toBeInTheDocument(); const items = await screen.findAllByTestId(/letter/i); expect(items).toHaveLength(2); - expect(getItemTexts()).toStrictEqual(['A', 'B']); + expect(getItemTexts()).toStrictEqual(["A", "B"]); - const button = screen.getByText('Fetch more'); + const button = screen.getByText("Fetch more"); const user = userEvent.setup(); await act(() => user.click(button)); @@ -3271,21 +3249,21 @@ describe('useBackgroundQuery', () => { const moreItems = await screen.findAllByTestId(/letter/i); expect(moreItems).toHaveLength(4); - expect(getItemTexts()).toStrictEqual(['A', 'B', 'C', 'D']); + expect(getItemTexts()).toStrictEqual(["A", "B", "C", "D"]); }); - it('properly uses cache field policies when calling `fetchMore` without `updateQuery`', async () => { + it("properly uses cache field policies when calling `fetchMore` without `updateQuery`", async () => { const { renders } = renderPaginatedIntegrationTest({ fieldPolicies: true, }); expect(renders.suspenseCount).toBe(1); - expect(screen.getByText('loading')).toBeInTheDocument(); + expect(screen.getByText("loading")).toBeInTheDocument(); const items = await screen.findAllByTestId(/letter/i); expect(items).toHaveLength(2); - expect(getItemTexts()).toStrictEqual(['A', 'B']); + expect(getItemTexts()).toStrictEqual(["A", "B"]); - const button = screen.getByText('Fetch more'); + const button = screen.getByText("Fetch more"); const user = userEvent.setup(); await act(() => user.click(button)); @@ -3297,15 +3275,15 @@ describe('useBackgroundQuery', () => { const moreItems = await screen.findAllByTestId(/letter/i); expect(moreItems).toHaveLength(4); - expect(getItemTexts()).toStrictEqual(['A', 'B', 'C', 'D']); + expect(getItemTexts()).toStrictEqual(["A", "B", "C", "D"]); }); - it('`fetchMore` works with startTransition to allow React to show stale UI until finished suspending', async () => { + it("`fetchMore` works with startTransition to allow React to show stale UI until finished suspending", async () => { type Variables = { offset: number; }; interface Todo { - __typename: 'Todo'; + __typename: "Todo"; id: string; name: string; completed: boolean; @@ -3332,9 +3310,9 @@ describe('useBackgroundQuery', () => { data: { todos: [ { - __typename: 'Todo', - id: '1', - name: 'Clean room', + __typename: "Todo", + id: "1", + name: "Clean room", completed: false, }, ], @@ -3348,9 +3326,9 @@ describe('useBackgroundQuery', () => { data: { todos: [ { - __typename: 'Todo', - id: '2', - name: 'Take out trash', + __typename: "Todo", + id: "2", + name: "Take out trash", completed: true, }, ], @@ -3420,7 +3398,7 @@ describe('useBackgroundQuery', () => { {todos.map((todo) => (
{todo.name} - {todo.completed && ' (completed)'} + {todo.completed && " (completed)"}
))}
@@ -3430,13 +3408,13 @@ describe('useBackgroundQuery', () => { render(); - expect(screen.getByText('Loading')).toBeInTheDocument(); + expect(screen.getByText("Loading")).toBeInTheDocument(); - expect(await screen.findByTestId('todos')).toBeInTheDocument(); + expect(await screen.findByTestId("todos")).toBeInTheDocument(); - const todos = screen.getByTestId('todos'); - const todo1 = screen.getByTestId('todo:1'); - const button = screen.getByText('Load more'); + const todos = screen.getByTestId("todos"); + const todo1 = screen.getByTestId("todo:1"); + const button = screen.getByText("Load more"); expect(todo1).toBeInTheDocument(); @@ -3449,21 +3427,21 @@ describe('useBackgroundQuery', () => { // Here we should not see the suspense fallback while the component suspends // until the todo is finished loading. Seeing the suspense fallback is an // indication that we are suspending the component too late in the process. - expect(screen.queryByText('Loading')).not.toBeInTheDocument(); + expect(screen.queryByText("Loading")).not.toBeInTheDocument(); // We can ensure this works with isPending from useTransition in the process - expect(todos).toHaveAttribute('aria-busy', 'true'); + expect(todos).toHaveAttribute("aria-busy", "true"); // Ensure we are showing the stale UI until the new todo has loaded - expect(todo1).toHaveTextContent('Clean room'); + expect(todo1).toHaveTextContent("Clean room"); // Eventually we should see the updated todos content once its done // suspending. await waitFor(() => { - expect(screen.getByTestId('todo:2')).toHaveTextContent( - 'Take out trash (completed)' + expect(screen.getByTestId("todo:2")).toHaveTextContent( + "Take out trash (completed)" ); - expect(todo1).toHaveTextContent('Clean room'); + expect(todo1).toHaveTextContent("Clean room"); }); }); @@ -3541,9 +3519,9 @@ describe('useBackgroundQuery', () => { > Refetch -
{data?.primes.join(', ')}
+
{data?.primes.join(", ")}
{networkStatus}
-
{error?.message || 'undefined'}
+
{error?.message || "undefined"}
); } @@ -3551,7 +3529,7 @@ describe('useBackgroundQuery', () => { function Parent() { const [queryRef, { refetch }] = useBackgroundQuery(query, { variables: { min: 0, max: 12 }, - refetchWritePolicy: 'merge', + refetchWritePolicy: "merge", }); return ; } @@ -3569,27 +3547,27 @@ describe('useBackgroundQuery', () => { render(); await waitFor(() => { - expect(screen.getByTestId('primes')).toHaveTextContent( - '2, 3, 5, 7, 11' + expect(screen.getByTestId("primes")).toHaveTextContent( + "2, 3, 5, 7, 11" ); }); - expect(screen.getByTestId('network-status')).toHaveTextContent( - '7' // ready + expect(screen.getByTestId("network-status")).toHaveTextContent( + "7" // ready ); - expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(screen.getByTestId("error")).toHaveTextContent("undefined"); expect(mergeParams).toEqual([[undefined, [2, 3, 5, 7, 11]]]); - await act(() => user.click(screen.getByText('Refetch'))); + await act(() => user.click(screen.getByText("Refetch"))); await waitFor(() => { - expect(screen.getByTestId('primes')).toHaveTextContent( - '2, 3, 5, 7, 11, 13, 17, 19, 23, 29' + expect(screen.getByTestId("primes")).toHaveTextContent( + "2, 3, 5, 7, 11, 13, 17, 19, 23, 29" ); }); - expect(screen.getByTestId('network-status')).toHaveTextContent( - '7' // ready + expect(screen.getByTestId("network-status")).toHaveTextContent( + "7" // ready ); - expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(screen.getByTestId("error")).toHaveTextContent("undefined"); expect(mergeParams).toEqual([ [undefined, [2, 3, 5, 7, 11]], [ @@ -3673,9 +3651,9 @@ describe('useBackgroundQuery', () => { > Refetch -
{data?.primes.join(', ')}
+
{data?.primes.join(", ")}
{networkStatus}
-
{error?.message || 'undefined'}
+
{error?.message || "undefined"}
); } @@ -3700,27 +3678,27 @@ describe('useBackgroundQuery', () => { render(); await waitFor(() => { - expect(screen.getByTestId('primes')).toHaveTextContent( - '2, 3, 5, 7, 11' + expect(screen.getByTestId("primes")).toHaveTextContent( + "2, 3, 5, 7, 11" ); }); - expect(screen.getByTestId('network-status')).toHaveTextContent( - '7' // ready + expect(screen.getByTestId("network-status")).toHaveTextContent( + "7" // ready ); - expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(screen.getByTestId("error")).toHaveTextContent("undefined"); expect(mergeParams).toEqual([[undefined, [2, 3, 5, 7, 11]]]); - await act(() => user.click(screen.getByText('Refetch'))); + await act(() => user.click(screen.getByText("Refetch"))); await waitFor(() => { - expect(screen.getByTestId('primes')).toHaveTextContent( - '13, 17, 19, 23, 29' + expect(screen.getByTestId("primes")).toHaveTextContent( + "13, 17, 19, 23, 29" ); }); - expect(screen.getByTestId('network-status')).toHaveTextContent( - '7' // ready + expect(screen.getByTestId("network-status")).toHaveTextContent( + "7" // ready ); - expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(screen.getByTestId("error")).toHaveTextContent("undefined"); expect(mergeParams).toEqual([ [undefined, [2, 3, 5, 7, 11]], [undefined, [13, 17, 19, 23, 29]], @@ -3754,7 +3732,7 @@ describe('useBackgroundQuery', () => { const mocks = [ { request: { query: fullQuery }, - result: { data: { character: { id: '1', name: 'Doctor Strange' } } }, + result: { data: { character: { id: "1", name: "Doctor Strange" } } }, }, ]; @@ -3775,7 +3753,7 @@ describe('useBackgroundQuery', () => { cache.writeQuery({ query: partialQuery, - data: { character: { id: '1' } }, + data: { character: { id: "1" } }, }); const client = new ApolloClient({ @@ -3800,7 +3778,7 @@ describe('useBackgroundQuery', () => { function Parent() { const [queryRef] = useBackgroundQuery(fullQuery, { - fetchPolicy: 'cache-first', + fetchPolicy: "cache-first", returnPartialData: true, }); return ; @@ -3819,7 +3797,7 @@ describe('useBackgroundQuery', () => {
{data.character?.id}
{data.character?.name}
{networkStatus}
-
{error?.message || 'undefined'}
+
{error?.message || "undefined"}
); } @@ -3827,19 +3805,19 @@ describe('useBackgroundQuery', () => { render(); expect(renders.suspenseCount).toBe(0); - expect(screen.getByTestId('character-id')).toHaveTextContent('1'); - expect(screen.getByTestId('character-name')).toHaveTextContent(''); - expect(screen.getByTestId('network-status')).toHaveTextContent('1'); // loading - expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(screen.getByTestId("character-id")).toHaveTextContent("1"); + expect(screen.getByTestId("character-name")).toHaveTextContent(""); + expect(screen.getByTestId("network-status")).toHaveTextContent("1"); // loading + expect(screen.getByTestId("error")).toHaveTextContent("undefined"); await waitFor(() => { - expect(screen.getByTestId('character-name')).toHaveTextContent( - 'Doctor Strange' + expect(screen.getByTestId("character-name")).toHaveTextContent( + "Doctor Strange" ); }); - expect(screen.getByTestId('character-id')).toHaveTextContent('1'); - expect(screen.getByTestId('network-status')).toHaveTextContent('7'); // ready - expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(screen.getByTestId("character-id")).toHaveTextContent("1"); + expect(screen.getByTestId("network-status")).toHaveTextContent("7"); // ready + expect(screen.getByTestId("error")).toHaveTextContent("undefined"); expect(renders.count).toBe(2); expect(renders.suspenseCount).toBe(0); @@ -3858,25 +3836,25 @@ describe('useBackgroundQuery', () => { cache.writeQuery({ query: partialQuery, - data: { character: { id: '1' } }, - variables: { id: '1' }, + data: { character: { id: "1" } }, + variables: { id: "1" }, }); const { renders, mocks, rerender } = renderVariablesIntegrationTest({ - variables: { id: '1' }, + variables: { id: "1" }, cache, options: { - fetchPolicy: 'cache-first', + fetchPolicy: "cache-first", returnPartialData: true, }, }); expect(renders.suspenseCount).toBe(0); - expect(await screen.findByText('1 - Spider-Man')).toBeInTheDocument(); + expect(await screen.findByText("1 - Spider-Man")).toBeInTheDocument(); - rerender({ variables: { id: '2' } }); + rerender({ variables: { id: "2" } }); - expect(await screen.findByText('2 - Black Widow')).toBeInTheDocument(); + expect(await screen.findByText("2 - Black Widow")).toBeInTheDocument(); expect(renders.frames[2]).toMatchObject({ ...mocks[1].result, @@ -3888,7 +3866,7 @@ describe('useBackgroundQuery', () => { expect(renders.suspenseCount).toBe(1); expect(renders.frames).toMatchObject([ { - data: { character: { id: '1' } }, + data: { character: { id: "1" } }, networkStatus: NetworkStatus.loading, error: undefined, }, @@ -3932,7 +3910,7 @@ describe('useBackgroundQuery', () => { const mocks = [ { request: { query: fullQuery }, - result: { data: { character: { id: '1', name: 'Doctor Strange' } } }, + result: { data: { character: { id: "1", name: "Doctor Strange" } } }, }, ]; @@ -3959,7 +3937,7 @@ describe('useBackgroundQuery', () => { cache.writeQuery({ query: partialQuery, - data: { character: { id: '1' } }, + data: { character: { id: "1" } }, }); const client = new ApolloClient({ @@ -3984,7 +3962,7 @@ describe('useBackgroundQuery', () => { function Parent() { const [queryRef] = useBackgroundQuery(fullQuery, { - fetchPolicy: 'network-only', + fetchPolicy: "network-only", returnPartialData: true, }); @@ -4004,7 +3982,7 @@ describe('useBackgroundQuery', () => {
{data.character?.id}
{data.character?.name}
{networkStatus}
-
{error?.message || 'undefined'}
+
{error?.message || "undefined"}
); } @@ -4014,13 +3992,13 @@ describe('useBackgroundQuery', () => { expect(renders.suspenseCount).toBe(1); await waitFor(() => { - expect(screen.getByTestId('character-name')).toHaveTextContent( - 'Doctor Strange' + expect(screen.getByTestId("character-name")).toHaveTextContent( + "Doctor Strange" ); }); - expect(screen.getByTestId('character-id')).toHaveTextContent('1'); - expect(screen.getByTestId('network-status')).toHaveTextContent('7'); // ready - expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(screen.getByTestId("character-id")).toHaveTextContent("1"); + expect(screen.getByTestId("network-status")).toHaveTextContent("7"); // ready + expect(screen.getByTestId("error")).toHaveTextContent("undefined"); expect(renders.count).toBe(1); expect(renders.suspenseCount).toBe(1); @@ -4035,7 +4013,7 @@ describe('useBackgroundQuery', () => { }); it('suspends when partial data is in the cache and using a "no-cache" fetch policy with returnPartialData', async () => { - const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); + const consoleSpy = jest.spyOn(console, "warn").mockImplementation(); interface Data { character: { id: string; @@ -4062,7 +4040,7 @@ describe('useBackgroundQuery', () => { const mocks = [ { request: { query: fullQuery }, - result: { data: { character: { id: '1', name: 'Doctor Strange' } } }, + result: { data: { character: { id: "1", name: "Doctor Strange" } } }, }, ]; @@ -4089,7 +4067,7 @@ describe('useBackgroundQuery', () => { cache.writeQuery({ query: partialQuery, - data: { character: { id: '1' } }, + data: { character: { id: "1" } }, }); const client = new ApolloClient({ @@ -4114,7 +4092,7 @@ describe('useBackgroundQuery', () => { function Parent() { const [queryRef] = useBackgroundQuery(fullQuery, { - fetchPolicy: 'no-cache', + fetchPolicy: "no-cache", returnPartialData: true, }); @@ -4134,7 +4112,7 @@ describe('useBackgroundQuery', () => {
{data.character?.id}
{data.character?.name}
{networkStatus}
-
{error?.message || 'undefined'}
+
{error?.message || "undefined"}
); } @@ -4144,13 +4122,13 @@ describe('useBackgroundQuery', () => { expect(renders.suspenseCount).toBe(1); await waitFor(() => { - expect(screen.getByTestId('character-name')).toHaveTextContent( - 'Doctor Strange' + expect(screen.getByTestId("character-name")).toHaveTextContent( + "Doctor Strange" ); }); - expect(screen.getByTestId('character-id')).toHaveTextContent('1'); - expect(screen.getByTestId('network-status')).toHaveTextContent('7'); // ready - expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(screen.getByTestId("character-id")).toHaveTextContent("1"); + expect(screen.getByTestId("network-status")).toHaveTextContent("7"); // ready + expect(screen.getByTestId("error")).toHaveTextContent("undefined"); expect(renders.count).toBe(1); expect(renders.suspenseCount).toBe(1); @@ -4167,7 +4145,7 @@ describe('useBackgroundQuery', () => { }); it('warns when using returnPartialData with a "no-cache" fetch policy', async () => { - const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); + const consoleSpy = jest.spyOn(console, "warn").mockImplementation(); const query: TypedDocumentNode = gql` query UserQuery { @@ -4177,14 +4155,14 @@ describe('useBackgroundQuery', () => { const mocks = [ { request: { query }, - result: { data: { greeting: 'Hello' } }, + result: { data: { greeting: "Hello" } }, }, ]; renderSuspenseHook( () => useBackgroundQuery(query, { - fetchPolicy: 'no-cache', + fetchPolicy: "no-cache", returnPartialData: true, }), { mocks } @@ -4192,7 +4170,7 @@ describe('useBackgroundQuery', () => { expect(console.warn).toHaveBeenCalledTimes(1); expect(console.warn).toHaveBeenCalledWith( - 'Using `returnPartialData` with a `no-cache` fetch policy has no effect. To read partial data from the cache, consider using an alternate fetch policy.' + "Using `returnPartialData` with a `no-cache` fetch policy has no effect. To read partial data from the cache, consider using an alternate fetch policy." ); consoleSpy.mockRestore(); @@ -4225,7 +4203,7 @@ describe('useBackgroundQuery', () => { const mocks = [ { request: { query: fullQuery }, - result: { data: { character: { id: '1', name: 'Doctor Strange' } } }, + result: { data: { character: { id: "1", name: "Doctor Strange" } } }, }, ]; @@ -4252,7 +4230,7 @@ describe('useBackgroundQuery', () => { cache.writeQuery({ query: partialQuery, - data: { character: { id: '1' } }, + data: { character: { id: "1" } }, }); const client = new ApolloClient({ @@ -4277,7 +4255,7 @@ describe('useBackgroundQuery', () => { function Parent() { const [queryRef] = useBackgroundQuery(fullQuery, { - fetchPolicy: 'cache-and-network', + fetchPolicy: "cache-and-network", returnPartialData: true, }); @@ -4297,7 +4275,7 @@ describe('useBackgroundQuery', () => {
{data.character?.id}
{data.character?.name}
{networkStatus}
-
{error?.message || 'undefined'}
+
{error?.message || "undefined"}
); } @@ -4305,27 +4283,27 @@ describe('useBackgroundQuery', () => { render(); expect(renders.suspenseCount).toBe(0); - expect(screen.getByTestId('character-id')).toHaveTextContent('1'); + expect(screen.getByTestId("character-id")).toHaveTextContent("1"); // name is not present yet, since it's missing in partial data - expect(screen.getByTestId('character-name')).toHaveTextContent(''); - expect(screen.getByTestId('network-status')).toHaveTextContent('1'); // loading - expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(screen.getByTestId("character-name")).toHaveTextContent(""); + expect(screen.getByTestId("network-status")).toHaveTextContent("1"); // loading + expect(screen.getByTestId("error")).toHaveTextContent("undefined"); await waitFor(() => { - expect(screen.getByTestId('character-name')).toHaveTextContent( - 'Doctor Strange' + expect(screen.getByTestId("character-name")).toHaveTextContent( + "Doctor Strange" ); }); - expect(screen.getByTestId('character-id')).toHaveTextContent('1'); - expect(screen.getByTestId('network-status')).toHaveTextContent('7'); // ready - expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(screen.getByTestId("character-id")).toHaveTextContent("1"); + expect(screen.getByTestId("network-status")).toHaveTextContent("7"); // ready + expect(screen.getByTestId("error")).toHaveTextContent("undefined"); expect(renders.count).toBe(2); expect(renders.suspenseCount).toBe(0); expect(renders.frames).toMatchObject([ { - data: { character: { id: '1' } }, + data: { character: { id: "1" } }, networkStatus: NetworkStatus.loading, error: undefined, }, @@ -4350,32 +4328,32 @@ describe('useBackgroundQuery', () => { cache.writeQuery({ query: partialQuery, - data: { character: { id: '1' } }, - variables: { id: '1' }, + data: { character: { id: "1" } }, + variables: { id: "1" }, }); const { renders, mocks, rerender } = renderVariablesIntegrationTest({ - variables: { id: '1' }, + variables: { id: "1" }, cache, options: { - fetchPolicy: 'cache-and-network', + fetchPolicy: "cache-and-network", returnPartialData: true, }, }); expect(renders.suspenseCount).toBe(0); - expect(await screen.findByText('1 - Spider-Man')).toBeInTheDocument(); + expect(await screen.findByText("1 - Spider-Man")).toBeInTheDocument(); - rerender({ variables: { id: '2' } }); + rerender({ variables: { id: "2" } }); - expect(await screen.findByText('2 - Black Widow')).toBeInTheDocument(); + expect(await screen.findByText("2 - Black Widow")).toBeInTheDocument(); expect(renders.count).toBe(3); expect(renders.suspenseCount).toBe(1); expect(renders.frames).toMatchObject([ { - data: { character: { id: '1' } }, + data: { character: { id: "1" } }, networkStatus: NetworkStatus.loading, error: undefined, }, @@ -4422,13 +4400,13 @@ describe('useBackgroundQuery', () => { // We are intentionally writing partial data to the cache. Supress console // warnings to avoid unnecessary noise in the test. - const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); + const consoleSpy = jest.spyOn(console, "error").mockImplementation(); cache.writeQuery({ query, data: { greeting: { - __typename: 'Greeting', - recipient: { __typename: 'Person', name: 'Cached Alice' }, + __typename: "Greeting", + recipient: { __typename: "Person", name: "Cached Alice" }, }, }, }); @@ -4475,7 +4453,7 @@ describe('useBackgroundQuery', () => { function Parent() { const [queryRef] = useBackgroundQuery(query, { - fetchPolicy: 'cache-first', + fetchPolicy: "cache-first", returnPartialData: true, }); @@ -4495,7 +4473,7 @@ describe('useBackgroundQuery', () => {
{data.greeting?.message}
{data.greeting?.recipient?.name}
{networkStatus}
-
{error?.message || 'undefined'}
+
{error?.message || "undefined"}
); } @@ -4503,37 +4481,37 @@ describe('useBackgroundQuery', () => { render(); expect(renders.suspenseCount).toBe(0); - expect(screen.getByTestId('recipient')).toHaveTextContent('Cached Alice'); + expect(screen.getByTestId("recipient")).toHaveTextContent("Cached Alice"); // message is not present yet, since it's missing in partial data - expect(screen.getByTestId('message')).toHaveTextContent(''); - expect(screen.getByTestId('network-status')).toHaveTextContent('1'); // loading - expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(screen.getByTestId("message")).toHaveTextContent(""); + expect(screen.getByTestId("network-status")).toHaveTextContent("1"); // loading + expect(screen.getByTestId("error")).toHaveTextContent("undefined"); link.simulateResult({ result: { data: { - greeting: { message: 'Hello world', __typename: 'Greeting' }, + greeting: { message: "Hello world", __typename: "Greeting" }, }, hasNext: true, }, }); await waitFor(() => { - expect(screen.getByTestId('message')).toHaveTextContent('Hello world'); + expect(screen.getByTestId("message")).toHaveTextContent("Hello world"); }); - expect(screen.getByTestId('recipient')).toHaveTextContent('Cached Alice'); - expect(screen.getByTestId('network-status')).toHaveTextContent('7'); // ready - expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(screen.getByTestId("recipient")).toHaveTextContent("Cached Alice"); + expect(screen.getByTestId("network-status")).toHaveTextContent("7"); // ready + expect(screen.getByTestId("error")).toHaveTextContent("undefined"); link.simulateResult({ result: { incremental: [ { data: { - __typename: 'Greeting', - recipient: { name: 'Alice', __typename: 'Person' }, + __typename: "Greeting", + recipient: { name: "Alice", __typename: "Person" }, }, - path: ['greeting'], + path: ["greeting"], }, ], hasNext: false, @@ -4541,11 +4519,11 @@ describe('useBackgroundQuery', () => { }); await waitFor(() => { - expect(screen.getByTestId('recipient').textContent).toEqual('Alice'); + expect(screen.getByTestId("recipient").textContent).toEqual("Alice"); }); - expect(screen.getByTestId('message')).toHaveTextContent('Hello world'); - expect(screen.getByTestId('network-status')).toHaveTextContent('7'); // ready - expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(screen.getByTestId("message")).toHaveTextContent("Hello world"); + expect(screen.getByTestId("network-status")).toHaveTextContent("7"); // ready + expect(screen.getByTestId("error")).toHaveTextContent("undefined"); expect(renders.count).toBe(3); expect(renders.suspenseCount).toBe(0); @@ -4553,8 +4531,8 @@ describe('useBackgroundQuery', () => { { data: { greeting: { - __typename: 'Greeting', - recipient: { __typename: 'Person', name: 'Cached Alice' }, + __typename: "Greeting", + recipient: { __typename: "Person", name: "Cached Alice" }, }, }, networkStatus: NetworkStatus.loading, @@ -4563,9 +4541,9 @@ describe('useBackgroundQuery', () => { { data: { greeting: { - __typename: 'Greeting', - message: 'Hello world', - recipient: { __typename: 'Person', name: 'Cached Alice' }, + __typename: "Greeting", + message: "Hello world", + recipient: { __typename: "Person", name: "Cached Alice" }, }, }, networkStatus: NetworkStatus.ready, @@ -4574,9 +4552,9 @@ describe('useBackgroundQuery', () => { { data: { greeting: { - __typename: 'Greeting', - message: 'Hello world', - recipient: { __typename: 'Person', name: 'Alice' }, + __typename: "Greeting", + message: "Hello world", + recipient: { __typename: "Person", name: "Alice" }, }, }, networkStatus: NetworkStatus.ready, @@ -4586,8 +4564,8 @@ describe('useBackgroundQuery', () => { }); }); - describe.skip('type tests', () => { - it('returns unknown when TData cannot be inferred', () => { + describe.skip("type tests", () => { + it("returns unknown when TData cannot be inferred", () => { const query = gql` query { hello @@ -4600,14 +4578,14 @@ describe('useBackgroundQuery', () => { expectTypeOf(data).toEqualTypeOf(); }); - it('disallows wider variables type than specified', () => { + it("disallows wider variables type than specified", () => { const { query } = useVariablesIntegrationTestCase(); // @ts-expect-error should not allow wider TVariables type - useBackgroundQuery(query, { variables: { id: '1', foo: 'bar' } }); + useBackgroundQuery(query, { variables: { id: "1", foo: "bar" } }); }); - it('returns TData in default case', () => { + it("returns TData in default case", () => { const { query } = useVariablesIntegrationTestCase(); const [inferredQueryRef] = useBackgroundQuery(query); @@ -4631,7 +4609,7 @@ describe('useBackgroundQuery', () => { const { query } = useVariablesIntegrationTestCase(); const [inferredQueryRef] = useBackgroundQuery(query, { - errorPolicy: 'ignore', + errorPolicy: "ignore", }); const { data: inferred } = useReadQuery(inferredQueryRef); @@ -4642,7 +4620,7 @@ describe('useBackgroundQuery', () => { VariablesCaseData, VariablesCaseVariables >(query, { - errorPolicy: 'ignore', + errorPolicy: "ignore", }); const { data: explicit } = useReadQuery(explicitQueryRef); @@ -4655,7 +4633,7 @@ describe('useBackgroundQuery', () => { const { query } = useVariablesIntegrationTestCase(); const [inferredQueryRef] = useBackgroundQuery(query, { - errorPolicy: 'all', + errorPolicy: "all", }); const { data: inferred } = useReadQuery(inferredQueryRef); @@ -4663,7 +4641,7 @@ describe('useBackgroundQuery', () => { expectTypeOf(inferred).not.toEqualTypeOf(); const [explicitQueryRef] = useBackgroundQuery(query, { - errorPolicy: 'all', + errorPolicy: "all", }); const { data: explicit } = useReadQuery(explicitQueryRef); @@ -4675,7 +4653,7 @@ describe('useBackgroundQuery', () => { const { query } = useVariablesIntegrationTestCase(); const [inferredQueryRef] = useBackgroundQuery(query, { - errorPolicy: 'none', + errorPolicy: "none", }); const { data: inferred } = useReadQuery(inferredQueryRef); @@ -4683,7 +4661,7 @@ describe('useBackgroundQuery', () => { expectTypeOf(inferred).not.toEqualTypeOf(); const [explicitQueryRef] = useBackgroundQuery(query, { - errorPolicy: 'none', + errorPolicy: "none", }); const { data: explicit } = useReadQuery(explicitQueryRef); @@ -4691,7 +4669,7 @@ describe('useBackgroundQuery', () => { expectTypeOf(explicit).not.toEqualTypeOf(); }); - it('returns DeepPartial with returnPartialData: true', () => { + it("returns DeepPartial with returnPartialData: true", () => { const { query } = useVariablesIntegrationTestCase(); const [inferredQueryRef] = useBackgroundQuery(query, { @@ -4715,7 +4693,7 @@ describe('useBackgroundQuery', () => { expectTypeOf(explicit).not.toEqualTypeOf(); }); - it('returns TData with returnPartialData: false', () => { + it("returns TData with returnPartialData: false", () => { const { query } = useVariablesIntegrationTestCase(); const [inferredQueryRef] = useBackgroundQuery(query, { @@ -4743,11 +4721,11 @@ describe('useBackgroundQuery', () => { >(); }); - it('returns TData when passing an option that does not affect TData', () => { + it("returns TData when passing an option that does not affect TData", () => { const { query } = useVariablesIntegrationTestCase(); const [inferredQueryRef] = useBackgroundQuery(query, { - fetchPolicy: 'no-cache', + fetchPolicy: "no-cache", }); const { data: inferred } = useReadQuery(inferredQueryRef); @@ -4760,7 +4738,7 @@ describe('useBackgroundQuery', () => { VariablesCaseData, VariablesCaseVariables >(query, { - fetchPolicy: 'no-cache', + fetchPolicy: "no-cache", }); const { data: explicit } = useReadQuery(explicitQueryRef); @@ -4771,12 +4749,12 @@ describe('useBackgroundQuery', () => { >(); }); - it('handles combinations of options', () => { + it("handles combinations of options", () => { const { query } = useVariablesIntegrationTestCase(); const [inferredPartialDataIgnoreQueryRef] = useBackgroundQuery(query, { returnPartialData: true, - errorPolicy: 'ignore', + errorPolicy: "ignore", }); const { data: inferredPartialDataIgnore } = useReadQuery( inferredPartialDataIgnoreQueryRef @@ -4794,7 +4772,7 @@ describe('useBackgroundQuery', () => { VariablesCaseVariables >(query, { returnPartialData: true, - errorPolicy: 'ignore', + errorPolicy: "ignore", }); const { data: explicitPartialDataIgnore } = useReadQuery( @@ -4810,7 +4788,7 @@ describe('useBackgroundQuery', () => { const [inferredPartialDataNoneQueryRef] = useBackgroundQuery(query, { returnPartialData: true, - errorPolicy: 'none', + errorPolicy: "none", }); const { data: inferredPartialDataNone } = useReadQuery( @@ -4829,7 +4807,7 @@ describe('useBackgroundQuery', () => { VariablesCaseVariables >(query, { returnPartialData: true, - errorPolicy: 'none', + errorPolicy: "none", }); const { data: explicitPartialDataNone } = useReadQuery( @@ -4844,13 +4822,13 @@ describe('useBackgroundQuery', () => { ).not.toEqualTypeOf(); }); - it('returns correct TData type when combined options that do not affect TData', () => { + it("returns correct TData type when combined options that do not affect TData", () => { const { query } = useVariablesIntegrationTestCase(); const [inferredQueryRef] = useBackgroundQuery(query, { - fetchPolicy: 'no-cache', + fetchPolicy: "no-cache", returnPartialData: true, - errorPolicy: 'none', + errorPolicy: "none", }); const { data: inferred } = useReadQuery(inferredQueryRef); @@ -4861,9 +4839,9 @@ describe('useBackgroundQuery', () => { VariablesCaseData, VariablesCaseVariables >(query, { - fetchPolicy: 'no-cache', + fetchPolicy: "no-cache", returnPartialData: true, - errorPolicy: 'none', + errorPolicy: "none", }); const { data: explicit } = useReadQuery(explicitQueryRef); @@ -4872,7 +4850,7 @@ describe('useBackgroundQuery', () => { expectTypeOf(explicit).not.toEqualTypeOf(); }); - it('returns TData | undefined when `skip` is present', () => { + it("returns TData | undefined when `skip` is present", () => { const { query } = useVariablesIntegrationTestCase(); const [inferredQueryRef] = useBackgroundQuery(query, { diff --git a/src/react/hooks/__tests__/useFragment.test.tsx b/src/react/hooks/__tests__/useFragment.test.tsx index b12e8b4f758..401d1f5f2ed 100644 --- a/src/react/hooks/__tests__/useFragment.test.tsx +++ b/src/react/hooks/__tests__/useFragment.test.tsx @@ -1,6 +1,12 @@ import * as React from "react"; -import { render, waitFor, screen, renderHook, within } from "@testing-library/react"; -import userEvent from '@testing-library/user-event'; +import { + render, + waitFor, + screen, + renderHook, + within, +} from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import { act } from "react-dom/test-utils"; import { UseFragmentOptions, useFragment } from "../useFragment"; @@ -21,7 +27,7 @@ import { import { useQuery } from "../useQuery"; import { concatPagination } from "../../../utilities"; import assert from "assert"; -import { expectTypeOf } from 'expect-type' +import { expectTypeOf } from "expect-type"; import { SubscriptionObserver } from "zen-observable-ts"; describe("useFragment", () => { @@ -90,7 +96,7 @@ describe("useFragment", () => { { __typename: "Item", id: 5 }, ], }, - }) + }); const renders: string[] = []; @@ -99,9 +105,7 @@ describe("useFragment", () => { const { loading, data } = useQuery(listQuery); expect(loading).toBe(false); return ( -
    - {data?.list.map(item => )} -
+
    {data?.list.map((item) => )}
); } @@ -127,24 +131,15 @@ describe("useFragment", () => { function getItemTexts() { return screen.getAllByText(/^Item/).map( // eslint-disable-next-line testing-library/no-node-access - li => li.firstChild!.textContent + (li) => li.firstChild!.textContent ); } await waitFor(() => { - expect(getItemTexts()).toEqual([ - "Item #1", - "Item #2", - "Item #5", - ]); + expect(getItemTexts()).toEqual(["Item #1", "Item #2", "Item #5"]); }); - expect(renders).toEqual([ - "list", - "item 1", - "item 2", - "item 5", - ]); + expect(renders).toEqual(["list", "item 1", "item 2", "item 5"]); act(() => { cache.writeFragment({ @@ -158,11 +153,7 @@ describe("useFragment", () => { }); await waitFor(() => { - expect(getItemTexts()).toEqual([ - "Item #1", - "Item #2 updated", - "Item #5", - ]); + expect(getItemTexts()).toEqual(["Item #1", "Item #2 updated", "Item #5"]); }); expect(renders).toEqual([ @@ -196,10 +187,11 @@ describe("useFragment", () => { text: "Item #4 from cache.modify", }, })!, - ].sort((ref1, ref2) => ( - readField("id", ref1)! - - readField("id", ref2)! - )); + ].sort( + (ref1, ref2) => + readField("id", ref1)! - + readField("id", ref2)! + ); }, }, }); @@ -302,47 +294,43 @@ describe("useFragment", () => { ], }, __META: { - extraRootIds: [ - "Item:2", - "Item:3", - "Item:4", - ], + extraRootIds: ["Item:2", "Item:3", "Item:4"], }, }); }); it("returns data on first render", () => { const ItemFragment: TypedDocumentNode = gql` - fragment ItemFragment on Item { - id - text - } + fragment ItemFragment on Item { + id + text + } `; const cache = new InMemoryCache(); - const item = { __typename: "Item", id: 1, text: "Item #1" }; + const item = { __typename: "Item", id: 1, text: "Item #1" }; cache.writeFragment({ fragment: ItemFragment, data: item, - }) + }); const client = new ApolloClient({ cache, - }) - function Component(){ + }); + function Component() { const { data } = useFragment({ fragment: ItemFragment, - from: { __typename: "Item", id: 1 } - }) - return <>{data.text} + from: { __typename: "Item", id: 1 }, + }); + return <>{data.text}; } render( - , +
); // would throw if not present synchronously screen.getByText(/Item #1/); - }) + }); it.each>([ // This query uses a basic field-level @nonreactive directive. @@ -388,335 +376,333 @@ describe("useFragment", () => { text } `, - ])("Parent list component can use @nonreactive to avoid rerendering", async (query) => { - const cache = new InMemoryCache({ - typePolicies: { - Query: { - fields: { - list: concatPagination(), + ])( + "Parent list component can use @nonreactive to avoid rerendering", + async (query) => { + const cache = new InMemoryCache({ + typePolicies: { + Query: { + fields: { + list: concatPagination(), + }, + }, + Item: { + keyFields: ["id"], + // Configuring keyArgs:false for Item.text is one way to prevent field + // keys like text@nonreactive, but it's not the only way. Since + // @nonreactive is now in the KNOWN_DIRECTIVES array defined in + // utilities/graphql/storeUtils.ts, the '@nonreactive' suffix won't be + // automatically appended to field keys by default. + // fields: { + // text: { + // keyArgs: false, + // }, + // }, }, }, - Item: { - keyFields: ["id"], - // Configuring keyArgs:false for Item.text is one way to prevent field - // keys like text@nonreactive, but it's not the only way. Since - // @nonreactive is now in the KNOWN_DIRECTIVES array defined in - // utilities/graphql/storeUtils.ts, the '@nonreactive' suffix won't be - // automatically appended to field keys by default. - // fields: { - // text: { - // keyArgs: false, - // }, - // }, - }, - }, - }); + }); - const client = new ApolloClient({ - cache, - link: ApolloLink.empty(), - }); + const client = new ApolloClient({ + cache, + link: ApolloLink.empty(), + }); - const renders: string[] = []; + const renders: string[] = []; - function List() { - const { data } = useQuery(query); + function List() { + const { data } = useQuery(query); - renders.push("list"); + renders.push("list"); - return ( -
    - {data?.list.map(item => )} -
- ); - } + return ( +
    + {data?.list.map((item) => )} +
+ ); + } - function Item({ item }: { item: Item }) { - const { data } = useFragment({ - fragment: ItemFragment, - fragmentName: "ItemFragment", - from: item, - }); + function Item({ item }: { item: Item }) { + const { data } = useFragment({ + fragment: ItemFragment, + fragmentName: "ItemFragment", + from: item, + }); - renders.push(`item ${item.id}`); + renders.push(`item ${item.id}`); - if (!data) return null; + if (!data) return null; - return
  • {`Item #${item.id}: ${data.text}`}
  • ; - } + return
  • {`Item #${item.id}: ${data.text}`}
  • ; + } - act(() => { - cache.writeQuery({ - query, - data: { + act(() => { + cache.writeQuery({ + query, + data: { + list: [ + { __typename: "Item", id: 1, text: "first" }, + { __typename: "Item", id: 2, text: "second" }, + { __typename: "Item", id: 3, text: "third" }, + ], + }, + }); + }); + + expect(cache.extract()).toEqual({ + ROOT_QUERY: { + __typename: "Query", list: [ - { __typename: "Item", id: 1, text: "first" }, - { __typename: "Item", id: 2, text: "second" }, - { __typename: "Item", id: 3, text: "third" }, + { __ref: 'Item:{"id":1}' }, + { __ref: 'Item:{"id":2}' }, + { __ref: 'Item:{"id":3}' }, ], }, + 'Item:{"id":1}': { + __typename: "Item", + id: 1, + text: "first", + }, + 'Item:{"id":2}': { + __typename: "Item", + id: 2, + text: "second", + }, + 'Item:{"id":3}': { + __typename: "Item", + id: 3, + text: "third", + }, }); - }); - - expect(cache.extract()).toEqual({ - ROOT_QUERY: { - __typename: "Query", - list: [ - { __ref: 'Item:{"id":1}' }, - { __ref: 'Item:{"id":2}' }, - { __ref: 'Item:{"id":3}' }, - ], - }, - 'Item:{"id":1}': { - __typename: "Item", - id: 1, - text: "first", - }, - 'Item:{"id":2}': { - __typename: "Item", - id: 2, - text: "second", - }, - 'Item:{"id":3}': { - __typename: "Item", - id: 3, - text: "third", - }, - }); - render( - - - , - ); + render( + + + + ); - function getItemTexts() { - return screen.getAllByText(/Item #\d+/).map(el => el.textContent); - } + function getItemTexts() { + return screen.getAllByText(/Item #\d+/).map((el) => el.textContent); + } - await waitFor(() => { - expect(getItemTexts()).toEqual([ - "Item #1: first", - "Item #2: second", - "Item #3: third", - ]); - }); + await waitFor(() => { + expect(getItemTexts()).toEqual([ + "Item #1: first", + "Item #2: second", + "Item #3: third", + ]); + }); - expect(renders).toEqual([ - "list", - "item 1", - "item 2", - "item 3", - ]); + expect(renders).toEqual(["list", "item 1", "item 2", "item 3"]); - function appendLyToText(id: number) { - act(() => { - cache.modify({ - id: cache.identify({ __typename: "Item", id })!, - fields: { - text(existing) { - return existing + "ly"; + function appendLyToText(id: number) { + act(() => { + cache.modify({ + id: cache.identify({ __typename: "Item", id })!, + fields: { + text(existing) { + return existing + "ly"; + }, }, - }, + }); }); - }); - } - - appendLyToText(2); - - await waitFor(() => { - expect(renders).toEqual([ - "list", - "item 1", - "item 2", - "item 3", - "item 2", - ]); - - expect(getItemTexts()).toEqual([ - "Item #1: first", - "Item #2: secondly", - "Item #3: third", - ]); - }); - - appendLyToText(1); - - await waitFor(() => { - expect(renders).toEqual([ - "list", - "item 1", - "item 2", - "item 3", - "item 2", - "item 1", - ]); + } - expect(getItemTexts()).toEqual([ - "Item #1: firstly", - "Item #2: secondly", - "Item #3: third", - ]); - }); + appendLyToText(2); + + await waitFor(() => { + expect(renders).toEqual([ + "list", + "item 1", + "item 2", + "item 3", + "item 2", + ]); + + expect(getItemTexts()).toEqual([ + "Item #1: first", + "Item #2: secondly", + "Item #3: third", + ]); + }); - appendLyToText(3); + appendLyToText(1); + + await waitFor(() => { + expect(renders).toEqual([ + "list", + "item 1", + "item 2", + "item 3", + "item 2", + "item 1", + ]); + + expect(getItemTexts()).toEqual([ + "Item #1: firstly", + "Item #2: secondly", + "Item #3: third", + ]); + }); - await waitFor(() => { - expect(renders).toEqual([ - "list", - "item 1", - "item 2", - "item 3", - "item 2", - "item 1", - "item 3", - ]); + appendLyToText(3); + + await waitFor(() => { + expect(renders).toEqual([ + "list", + "item 1", + "item 2", + "item 3", + "item 2", + "item 1", + "item 3", + ]); + + expect(getItemTexts()).toEqual([ + "Item #1: firstly", + "Item #2: secondly", + "Item #3: thirdly", + ]); + }); - expect(getItemTexts()).toEqual([ - "Item #1: firstly", - "Item #2: secondly", - "Item #3: thirdly", - ]); - }); + act(() => { + cache.writeQuery({ + query, + data: { + list: [ + { __typename: "Item", id: 4, text: "fourth" }, + { __typename: "Item", id: 5, text: "fifth" }, + ], + }, + }); + }); - act(() => { - cache.writeQuery({ - query, - data: { + expect(cache.extract()).toEqual({ + ROOT_QUERY: { + __typename: "Query", list: [ - { __typename: "Item", id: 4, text: "fourth" }, - { __typename: "Item", id: 5, text: "fifth" }, + { __ref: 'Item:{"id":1}' }, + { __ref: 'Item:{"id":2}' }, + { __ref: 'Item:{"id":3}' }, + { __ref: 'Item:{"id":4}' }, + { __ref: 'Item:{"id":5}' }, ], }, + 'Item:{"id":1}': { + __typename: "Item", + id: 1, + text: "firstly", + }, + 'Item:{"id":2}': { + __typename: "Item", + id: 2, + text: "secondly", + }, + 'Item:{"id":3}': { + __typename: "Item", + id: 3, + text: "thirdly", + }, + 'Item:{"id":4}': { + __typename: "Item", + id: 4, + text: "fourth", + }, + 'Item:{"id":5}': { + __typename: "Item", + id: 5, + text: "fifth", + }, }); - }); - expect(cache.extract()).toEqual({ - ROOT_QUERY: { - __typename: "Query", - list: [ - { __ref: 'Item:{"id":1}' }, - { __ref: 'Item:{"id":2}' }, - { __ref: 'Item:{"id":3}' }, - { __ref: 'Item:{"id":4}' }, - { __ref: 'Item:{"id":5}' }, - ], - }, - 'Item:{"id":1}': { - __typename: "Item", - id: 1, - text: "firstly", - }, - 'Item:{"id":2}': { - __typename: "Item", - id: 2, - text: "secondly", - }, - 'Item:{"id":3}': { - __typename: "Item", - id: 3, - text: "thirdly", - }, - 'Item:{"id":4}': { - __typename: "Item", - id: 4, - text: "fourth", - }, - 'Item:{"id":5}': { - __typename: "Item", - id: 5, - text: "fifth", - }, - }); - - await waitFor(() => { - expect(renders).toEqual([ - "list", - "item 1", - "item 2", - "item 3", - "item 2", - "item 1", - "item 3", - // The whole list had to be rendered again to append 4 and 5 - "list", - "item 1", - "item 2", - "item 3", - "item 4", - "item 5", - ]); - - expect(getItemTexts()).toEqual([ - "Item #1: firstly", - "Item #2: secondly", - "Item #3: thirdly", - "Item #4: fourth", - "Item #5: fifth", - ]); - }); - - appendLyToText(5); - - await waitFor(() => { - expect(renders).toEqual([ - "list", - "item 1", - "item 2", - "item 3", - "item 2", - "item 1", - "item 3", - "list", - "item 1", - "item 2", - "item 3", - "item 4", - "item 5", - // A single new render: - "item 5", - ]); - - expect(getItemTexts()).toEqual([ - "Item #1: firstly", - "Item #2: secondly", - "Item #3: thirdly", - "Item #4: fourth", - "Item #5: fifthly", - ]); - }); - - appendLyToText(4); + await waitFor(() => { + expect(renders).toEqual([ + "list", + "item 1", + "item 2", + "item 3", + "item 2", + "item 1", + "item 3", + // The whole list had to be rendered again to append 4 and 5 + "list", + "item 1", + "item 2", + "item 3", + "item 4", + "item 5", + ]); + + expect(getItemTexts()).toEqual([ + "Item #1: firstly", + "Item #2: secondly", + "Item #3: thirdly", + "Item #4: fourth", + "Item #5: fifth", + ]); + }); - await waitFor(() => { - expect(renders).toEqual([ - "list", - "item 1", - "item 2", - "item 3", - "item 2", - "item 1", - "item 3", - "list", - "item 1", - "item 2", - "item 3", - "item 4", - "item 5", - "item 5", - // A single new render: - "item 4", - ]); + appendLyToText(5); + + await waitFor(() => { + expect(renders).toEqual([ + "list", + "item 1", + "item 2", + "item 3", + "item 2", + "item 1", + "item 3", + "list", + "item 1", + "item 2", + "item 3", + "item 4", + "item 5", + // A single new render: + "item 5", + ]); + + expect(getItemTexts()).toEqual([ + "Item #1: firstly", + "Item #2: secondly", + "Item #3: thirdly", + "Item #4: fourth", + "Item #5: fifthly", + ]); + }); - expect(getItemTexts()).toEqual([ - "Item #1: firstly", - "Item #2: secondly", - "Item #3: thirdly", - "Item #4: fourthly", - "Item #5: fifthly", - ]); - }); - }); + appendLyToText(4); + + await waitFor(() => { + expect(renders).toEqual([ + "list", + "item 1", + "item 2", + "item 3", + "item 2", + "item 1", + "item 3", + "list", + "item 1", + "item 2", + "item 3", + "item 4", + "item 5", + "item 5", + // A single new render: + "item 4", + ]); + + expect(getItemTexts()).toEqual([ + "Item #1: firstly", + "Item #2: secondly", + "Item #3: thirdly", + "Item #4: fourthly", + "Item #5: fifthly", + ]); + }); + } + ); it("List can use useFragment with ListFragment", async () => { const cache = new InMemoryCache({ @@ -752,7 +738,7 @@ describe("useFragment", () => { ], extra: "from ListFragment", }, - }) + }); const renders: string[] = []; @@ -763,10 +749,12 @@ describe("useFragment", () => { from: { __typename: "Query" }, }); expect(complete).toBe(true); - assert(!!complete) + assert(!!complete); return (
      - {data.list.map(item => )} + {data.list.map((item) => ( + + ))}
    ); } @@ -792,24 +780,15 @@ describe("useFragment", () => { function getItemTexts() { return screen.getAllByText(/^Item/).map( // eslint-disable-next-line testing-library/no-node-access - li => li.firstChild!.textContent + (li) => li.firstChild!.textContent ); } await waitFor(() => { - expect(getItemTexts()).toEqual([ - "Item #1", - "Item #2", - "Item #5", - ]); + expect(getItemTexts()).toEqual(["Item #1", "Item #2", "Item #5"]); }); - expect(renders).toEqual([ - "list", - "item 1", - "item 2", - "item 5", - ]); + expect(renders).toEqual(["list", "item 1", "item 2", "item 5"]); act(() => { cache.writeFragment({ @@ -823,11 +802,7 @@ describe("useFragment", () => { }); await waitFor(() => { - expect(getItemTexts()).toEqual([ - "Item #1", - "Item #2 updated", - "Item #5", - ]); + expect(getItemTexts()).toEqual(["Item #1", "Item #2 updated", "Item #5"]); }); expect(renders).toEqual([ @@ -859,10 +834,11 @@ describe("useFragment", () => { id: 4, }, })!, - ].sort((ref1, ref2) => ( - readField("id", ref1)! - - readField("id", ref2)! - )); + ].sort( + (ref1, ref2) => + readField("id", ref1)! - + readField("id", ref2)! + ); }, }, }); @@ -965,11 +941,7 @@ describe("useFragment", () => { extra: "from ListFragment", }, __META: { - extraRootIds: [ - "Item:2", - "Item:3", - "Item:4", - ], + extraRootIds: ["Item:2", "Item:3", "Item:4"], }, }); }); @@ -987,9 +959,9 @@ describe("useFragment", () => { // filtering explicitly here, so this test won't be broken. return items && items.filter(canRead); }, - } - } - } + }, + }, + }, }); const wrapper = ({ children }: any) => ( @@ -1024,18 +996,18 @@ describe("useFragment", () => { `; const { result: renderResult } = renderHook( - () => useFragment({ - fragment: ListAndItemFragments, - fragmentName: "ListFragment", - from: { __typename: "Query" }, - }), - { wrapper }, + () => + useFragment({ + fragment: ListAndItemFragments, + fragmentName: "ListFragment", + from: { __typename: "Query" }, + }), + { wrapper } ); function checkHistory(expectedResultCount: number) { // Temporarily disabling this check until we can come up with a better // (more opt-in) system than result.previousResult.previousResult... - // function historyToArray( // result: UseFragmentResult, // ): UseFragmentResult[] { @@ -1048,7 +1020,6 @@ describe("useFragment", () => { // const all = historyToArray(renderResult.current); // expect(all.length).toBe(expectedResultCount); // expect(all).toEqual(renderResult.all); - // if (renderResult.current.complete) { // expect(renderResult.current).toBe( // renderResult.current.lastCompleteResult @@ -1118,37 +1089,43 @@ describe("useFragment", () => { }); }); - await waitFor(() => expect(renderResult.current.data).toEqual(data182WithText)); + await waitFor(() => + expect(renderResult.current.data).toEqual(data182WithText) + ); expect(renderResult.current.complete).toBe(true); expect(renderResult.current.missing).toBeUndefined(); checkHistory(3); - await act(async () => cache.batch({ - update(cache) { - cache.evict({ - id: cache.identify({ - __typename: "Item", - id: 8, - }), - }); - - cache.evict({ - id: cache.identify({ - __typename: "Item", - id: 2, - }), - fieldName: "text", - }); - }, - })); + await act(async () => + cache.batch({ + update(cache) { + cache.evict({ + id: cache.identify({ + __typename: "Item", + id: 8, + }), + }); + + cache.evict({ + id: cache.identify({ + __typename: "Item", + id: 2, + }), + fieldName: "text", + }); + }, + }) + ); - await waitFor(() => expect(renderResult.current.data).toEqual({ - list: [ - { __typename: "Item", id: 1, text: "oyez1" }, - { __typename: "Item", id: 2 }, - ], - })); + await waitFor(() => + expect(renderResult.current.data).toEqual({ + list: [ + { __typename: "Item", id: 1, text: "oyez1" }, + { __typename: "Item", id: 2 }, + ], + }) + ); expect(renderResult.current.complete).toBe(false); expect(renderResult.current.missing).toEqual({ // TODO Figure out why Item:8 is not represented here. Likely because of @@ -1179,11 +1156,7 @@ describe("useFragment", () => { }, ROOT_QUERY: { __typename: "Query", - list: [ - { __ref: "Item:1" }, - { __ref: "Item:8" }, - { __ref: "Item:2" }, - ], + list: [{ __ref: "Item:1" }, { __ref: "Item:8" }, { __ref: "Item:2" }], }, }); @@ -1213,27 +1186,29 @@ describe("useFragment", () => { const client = new ApolloClient({ cache, - link: new ApolloLink(operation => new Observable(observer => { - if (operation.operationName === "ListQueryWithItemFragment") { - setTimeout(() => { - observer.next({ - data: { - list: [ - { __typename: "Item", id: 1 }, - { __typename: "Item", id: 2 }, - { __typename: "Item", id: 5 }, - ], - } - }); - observer.complete(); - }, 10); - } else { - observer.error(`unexpected query ${ - operation.operationName || - operation.query - }`); - } - })), + link: new ApolloLink( + (operation) => + new Observable((observer) => { + if (operation.operationName === "ListQueryWithItemFragment") { + setTimeout(() => { + observer.next({ + data: { + list: [ + { __typename: "Item", id: 1 }, + { __typename: "Item", id: 2 }, + { __typename: "Item", id: 5 }, + ], + }, + }); + observer.complete(); + }, 10); + } else { + observer.error( + `unexpected query ${operation.operationName || operation.query}` + ); + } + }) + ), }); const listQuery: TypedDocumentNode = gql` @@ -1257,16 +1232,24 @@ describe("useFragment", () => { return complete ? ( <> - { + setCurrentItem(parseInt(e.currentTarget.value)); + }} + > + {data.list.map((item) => ( + + ))}
      - {data.list.map(item => )} + {data.list.map((item) => ( + + ))}
    ) : null; @@ -1292,7 +1275,7 @@ describe("useFragment", () => { function getItemTexts() { return screen.getAllByText(/^Item/).map( // eslint-disable-next-line testing-library/no-node-access - li => li.firstChild!.textContent + (li) => li.firstChild!.textContent ); } @@ -1310,8 +1293,8 @@ describe("useFragment", () => { // Select "Item #2" via