From 42dd03f86f930ebbb75ff62ce20e755be8051a5f Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Fri, 28 Sep 2018 09:39:53 -0400 Subject: [PATCH 1/4] Removing `lodash` as a dependency We're only depending on `flowRight`, so these changes remove the dependency on `lodash`, switching the dependency to `lodash.flowright`. React Apollo 3.0 will be removing this dependency completely, but for now this will help with bundle sizes. All development `lodash` use has also been adjusted to use individually imported `lodash` modules. Those will likely be removed as well in React Apollo 3.0. --- dangerfile.ts | 2 +- package-lock.json | 99 ++++++++++++------- package.json | 10 +- src/browser.ts | 2 +- src/test-links.ts | 2 +- test/client/getDataFromTree.test.tsx | 2 +- .../client/graphql/shared-operations.test.tsx | 6 +- 7 files changed, 78 insertions(+), 45 deletions(-) diff --git a/dangerfile.ts b/dangerfile.ts index bbee14f245..c3957aa145 100644 --- a/dangerfile.ts +++ b/dangerfile.ts @@ -1,5 +1,5 @@ // Removed import -import includes from 'lodash/includes'; +import includes from 'lodash.includes'; import fs from 'fs'; // Setup diff --git a/package-lock.json b/package-lock.json index abc5fe1f14..4fb485c700 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-apollo", - "version": "2.1.11", + "version": "2.2.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -185,6 +185,42 @@ "integrity": "sha512-lRnAtKnxMXcYYXqOiotTmJd74uawNWuPnsnPrrO7HiFuE3npE2iQhfABatbYDyxTNqZNuXzcKGhw37R7RjBFLg==", "dev": true }, + "@types/lodash.flowright": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@types/lodash.flowright/-/lodash.flowright-3.5.4.tgz", + "integrity": "sha512-gvKx4eK3uwb7geQQvJlTiz4gA/uTuaOQWuDDbBvlEOr/aVnS2U+J5FDJ1YYz2msmD1BdlTRdi95wUSDfGhwkxQ==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.includes": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/lodash.includes/-/lodash.includes-4.3.4.tgz", + "integrity": "sha512-1si/lL2IfNztD5IhXUgS6hqCTkmD2Sjjmft3uhBmXKfyox2tPz3U00I1ymQf+jGSxqM256td0SRKCmhpYPVmuw==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.isequal": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.3.tgz", + "integrity": "sha512-tpTUmHksO2H9RF98Y2w7v06ZeEKAxHPo2ysL0bV5qv5UBweiZl33NFu5QYmYOSxSnHMqBt/vsVsBVeQAcJiokg==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.times": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/lodash.times/-/lodash.times-4.3.4.tgz", + "integrity": "sha512-so9LquIiukPrJukKo6nekkmogpasm9k9EoWcbVmJ3QUzFEpLwlzXahMQZ5R/dWEpm5fCjiFVm/RDdmnR0QN9Ig==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/node": { "version": "9.4.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-9.4.6.tgz", @@ -3541,8 +3577,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -3563,14 +3598,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3585,20 +3618,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3715,8 +3745,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3728,7 +3757,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3743,7 +3771,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3751,14 +3778,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -3777,7 +3802,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3858,8 +3882,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3871,7 +3894,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3957,8 +3979,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3994,7 +4015,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4014,7 +4034,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4058,14 +4077,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -6481,7 +6498,8 @@ "lodash": { "version": "4.17.10", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true }, "lodash.escape": { "version": "4.0.1", @@ -6501,6 +6519,11 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.flowright": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/lodash.flowright/-/lodash.flowright-3.5.0.tgz", + "integrity": "sha1-K1//OZcW1+fcVyT+k0n2cGUYTWc=" + }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -6573,6 +6596,12 @@ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, + "lodash.times": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.times/-/lodash.times-4.3.2.tgz", + "integrity": "sha1-Ph8lZcQxdU1Uq1fy7RdBk5KFyh0=", + "dev": true + }, "log-driver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", @@ -10041,15 +10070,13 @@ "version": "2.17.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true, - "optional": true + "dev": true }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true + "dev": true } } }, diff --git a/package.json b/package.json index a3d7b5fe83..4965c57685 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,10 @@ "@types/graphql": "0.12.7", "@types/invariant": "2.2.29", "@types/jest": "23.3.2", - "@types/lodash": "4.14.116", + "@types/lodash.flowright": "^3.5.4", + "@types/lodash.includes": "^4.3.4", + "@types/lodash.isequal": "^4.5.3", + "@types/lodash.times": "^4.3.4", "@types/object-assign": "4.0.30", "@types/prop-types": "15.5.5", "@types/react": "16.4.14", @@ -122,6 +125,9 @@ "jest": "23.6.0", "jest-junit": "5.1.0", "jsdom": "12.0.0", + "lodash.includes": "^4.3.0", + "lodash.isequal": "^4.5.0", + "lodash.times": "^4.3.2", "preact": "8.3.1", "preact-compat": "3.18.4", "prettier": "1.14.3", @@ -148,7 +154,7 @@ "fbjs": "^1.0.0", "hoist-non-react-statics": "^3.0.0", "invariant": "^2.2.2", - "lodash": "^4.17.10", + "lodash.flowright": "^3.5.0", "prop-types": "^15.6.0" } } diff --git a/src/browser.ts b/src/browser.ts index f560dd7d82..6baafdc221 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -24,5 +24,5 @@ export * from './withApollo'; export * from './types'; // XXX remove in the next breaking semver change (3.0) -import compose from 'lodash/flowRight'; +import compose from 'lodash.flowright'; export { compose }; diff --git a/src/test-links.ts b/src/test-links.ts index 7e01456c47..45a0e0034b 100644 --- a/src/test-links.ts +++ b/src/test-links.ts @@ -9,7 +9,7 @@ import { import { print } from 'graphql/language/printer'; import { addTypenameToDocument } from 'apollo-utilities'; -import { isEqual } from 'lodash'; +import isEqual from 'lodash.isequal'; export interface MockedResponse { request: GraphQLRequest; diff --git a/test/client/getDataFromTree.test.tsx b/test/client/getDataFromTree.test.tsx index d86da0beff..f678950f95 100644 --- a/test/client/getDataFromTree.test.tsx +++ b/test/client/getDataFromTree.test.tsx @@ -12,7 +12,7 @@ import { ChildProps, } from '../../src'; import gql from 'graphql-tag'; -import times from 'lodash/times'; +import times from 'lodash.times'; import { InMemoryCache as Cache } from 'apollo-cache-inmemory'; import { mockSingleLink } from '../../src/test-utils'; import { DocumentNode } from 'graphql'; diff --git a/test/client/graphql/shared-operations.test.tsx b/test/client/graphql/shared-operations.test.tsx index ac0258d230..e6dbb82782 100644 --- a/test/client/graphql/shared-operations.test.tsx +++ b/test/client/graphql/shared-operations.test.tsx @@ -4,12 +4,12 @@ import gql from 'graphql-tag'; import ApolloClient from 'apollo-client'; import { InMemoryCache as Cache } from 'apollo-cache-inmemory'; import { ApolloLink } from 'apollo-link'; -import { mockSingleLink } from '../../../src/test-utils'; -import { ApolloProvider, ChildProps, DataValue, graphql, withApollo } from '../../../src'; import TestUtils from 'react-dom/test-utils'; import { DocumentNode } from 'graphql'; +import compose from 'lodash.flowRight'; -const compose = require('lodash/flowRight'); +import { mockSingleLink } from '../../../src/test-utils'; +import { ApolloProvider, ChildProps, DataValue, graphql, withApollo } from '../../../src'; describe('shared operations', () => { describe('withApollo', () => { From 5fc3a52879a3cacbcda771f323dbe70c22e25ca6 Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Fri, 28 Sep 2018 09:49:04 -0400 Subject: [PATCH 2/4] Fix module name typo --- test/client/graphql/shared-operations.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/client/graphql/shared-operations.test.tsx b/test/client/graphql/shared-operations.test.tsx index e6dbb82782..1226f32199 100644 --- a/test/client/graphql/shared-operations.test.tsx +++ b/test/client/graphql/shared-operations.test.tsx @@ -6,7 +6,7 @@ import { InMemoryCache as Cache } from 'apollo-cache-inmemory'; import { ApolloLink } from 'apollo-link'; import TestUtils from 'react-dom/test-utils'; import { DocumentNode } from 'graphql'; -import compose from 'lodash.flowRight'; +import compose from 'lodash.flowright'; import { mockSingleLink } from '../../../src/test-utils'; import { ApolloProvider, ChildProps, DataValue, graphql, withApollo } from '../../../src'; From ee430a74ec25183c37622fb67cb8404759049573 Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Fri, 28 Sep 2018 09:49:58 -0400 Subject: [PATCH 3/4] Fix file line endings --- dangerfile.ts | 200 +++++++++++++-------------- src/test-links.ts | 344 +++++++++++++++++++++++----------------------- 2 files changed, 272 insertions(+), 272 deletions(-) diff --git a/dangerfile.ts b/dangerfile.ts index c3957aa145..b480288087 100644 --- a/dangerfile.ts +++ b/dangerfile.ts @@ -1,100 +1,100 @@ -// Removed import -import includes from 'lodash.includes'; -import fs from 'fs'; - -// Setup -const pr = danger.github.pr; -const commits = danger.github.commits; -const modified = danger.git.modified_files; -const bodyAndTitle = (pr.body + pr.title).toLowerCase(); - -// Custom modifiers for people submitting PRs to be able to say "skip this" -const trivialPR = bodyAndTitle.includes('trivial'); -const acceptedNoTests = bodyAndTitle.includes('skip new tests'); - -const typescriptOnly = (file: string) => includes(file, '.ts'); -const filesOnly = (file: string) => fs.existsSync(file) && fs.lstatSync(file).isFile(); - -// Custom subsets of known files -const modifiedAppFiles = modified - .filter(p => includes(p, 'src/')) - .filter(p => filesOnly(p) && typescriptOnly(p)); - -const modifiedTestFiles = modified.filter(p => includes(p, 'test/')); - -// Takes a list of file paths, and converts it into clickable links -const linkableFiles = paths => { - const repoURL = danger.github.pr.head.repo.html_url; - const ref = danger.github.pr.head.ref; - const links = paths.map(path => { - return createLink(`${repoURL}/blob/${ref}/${path}`, path); - }); - return toSentence(links); -}; - -// ["1", "2", "3"] to "1, 2 and 3" -const toSentence = (array: Array): string => { - if (array.length === 1) { - return array[0]; - } - return array.slice(0, array.length - 1).join(', ') + ' and ' + array.pop(); -}; - -// ("/href/thing", "name") to "name" -const createLink = (href: string, text: string): string => `${text}`; - -// Raise about missing code inside files -const raiseIssueAboutPaths = (type: Function, paths: string[], codeToInclude: string) => { - if (paths.length > 0) { - const files = linkableFiles(paths); - const strict = '' + codeToInclude + ''; - type(`Please ensure that ${strict} is enabled on: ${files}`); - } -}; - -const authors = commits.map(x => x.author && x.author.login); -const isBot = authors.some(x => ['greenkeeper', 'renovate'].indexOf(x) > -1); - -if (!isBot) { - // Rules - // When there are app-changes and it's not a PR marked as trivial, expect - // there to be CHANGELOG changes. - const changelogChanges = includes(modified, 'Changelog.md'); - if (modifiedAppFiles.length > 0 && !trivialPR && !changelogChanges) { - fail('No CHANGELOG added.'); - } - - // No PR is too small to warrant a paragraph or two of summary - if (pr.body.length === 0) { - fail('Please add a description to your PR.'); - } - - const hasAppChanges = modifiedAppFiles.length > 0; - - const hasTestChanges = modifiedTestFiles.length > 0; - - // Warn when there is a big PR - const bigPRThreshold = 500; - if (danger.github.pr.additions + danger.github.pr.deletions > bigPRThreshold) { - warn(':exclamation: Big PR'); - } - - // Warn if there are library changes, but not tests - if (hasAppChanges && !hasTestChanges) { - warn( - "There are library changes, but not tests. That's OK as long as you're refactoring existing code", - ); - } - - // Be careful of leaving testing shortcuts in the codebase - const onlyTestFiles = modifiedTestFiles.filter(x => { - const content = fs.readFileSync(x).toString(); - return ( - content.includes('it.only') || - content.includes('describe.only') || - content.includes('fdescribe') || - content.includes('fit(') - ); - }); - raiseIssueAboutPaths(fail, onlyTestFiles, 'an `only` was left in the test'); -} +// Removed import +import includes from 'lodash.includes'; +import fs from 'fs'; + +// Setup +const pr = danger.github.pr; +const commits = danger.github.commits; +const modified = danger.git.modified_files; +const bodyAndTitle = (pr.body + pr.title).toLowerCase(); + +// Custom modifiers for people submitting PRs to be able to say "skip this" +const trivialPR = bodyAndTitle.includes('trivial'); +const acceptedNoTests = bodyAndTitle.includes('skip new tests'); + +const typescriptOnly = (file: string) => includes(file, '.ts'); +const filesOnly = (file: string) => fs.existsSync(file) && fs.lstatSync(file).isFile(); + +// Custom subsets of known files +const modifiedAppFiles = modified + .filter(p => includes(p, 'src/')) + .filter(p => filesOnly(p) && typescriptOnly(p)); + +const modifiedTestFiles = modified.filter(p => includes(p, 'test/')); + +// Takes a list of file paths, and converts it into clickable links +const linkableFiles = paths => { + const repoURL = danger.github.pr.head.repo.html_url; + const ref = danger.github.pr.head.ref; + const links = paths.map(path => { + return createLink(`${repoURL}/blob/${ref}/${path}`, path); + }); + return toSentence(links); +}; + +// ["1", "2", "3"] to "1, 2 and 3" +const toSentence = (array: Array): string => { + if (array.length === 1) { + return array[0]; + } + return array.slice(0, array.length - 1).join(', ') + ' and ' + array.pop(); +}; + +// ("/href/thing", "name") to "name" +const createLink = (href: string, text: string): string => `${text}`; + +// Raise about missing code inside files +const raiseIssueAboutPaths = (type: Function, paths: string[], codeToInclude: string) => { + if (paths.length > 0) { + const files = linkableFiles(paths); + const strict = '' + codeToInclude + ''; + type(`Please ensure that ${strict} is enabled on: ${files}`); + } +}; + +const authors = commits.map(x => x.author && x.author.login); +const isBot = authors.some(x => ['greenkeeper', 'renovate'].indexOf(x) > -1); + +if (!isBot) { + // Rules + // When there are app-changes and it's not a PR marked as trivial, expect + // there to be CHANGELOG changes. + const changelogChanges = includes(modified, 'Changelog.md'); + if (modifiedAppFiles.length > 0 && !trivialPR && !changelogChanges) { + fail('No CHANGELOG added.'); + } + + // No PR is too small to warrant a paragraph or two of summary + if (pr.body.length === 0) { + fail('Please add a description to your PR.'); + } + + const hasAppChanges = modifiedAppFiles.length > 0; + + const hasTestChanges = modifiedTestFiles.length > 0; + + // Warn when there is a big PR + const bigPRThreshold = 500; + if (danger.github.pr.additions + danger.github.pr.deletions > bigPRThreshold) { + warn(':exclamation: Big PR'); + } + + // Warn if there are library changes, but not tests + if (hasAppChanges && !hasTestChanges) { + warn( + "There are library changes, but not tests. That's OK as long as you're refactoring existing code", + ); + } + + // Be careful of leaving testing shortcuts in the codebase + const onlyTestFiles = modifiedTestFiles.filter(x => { + const content = fs.readFileSync(x).toString(); + return ( + content.includes('it.only') || + content.includes('describe.only') || + content.includes('fdescribe') || + content.includes('fit(') + ); + }); + raiseIssueAboutPaths(fail, onlyTestFiles, 'an `only` was left in the test'); +} diff --git a/src/test-links.ts b/src/test-links.ts index 45a0e0034b..f3730a020e 100644 --- a/src/test-links.ts +++ b/src/test-links.ts @@ -1,172 +1,172 @@ -import { - Operation, - GraphQLRequest, - ApolloLink, - FetchResult, - Observable, - // Observer, -} from 'apollo-link'; - -import { print } from 'graphql/language/printer'; -import { addTypenameToDocument } from 'apollo-utilities'; -import isEqual from 'lodash.isequal'; - -export interface MockedResponse { - request: GraphQLRequest; - result?: FetchResult; - error?: Error; - delay?: number; - newData?: () => FetchResult; -} - -export interface MockedSubscriptionResult { - result?: FetchResult; - error?: Error; - delay?: number; -} - -export interface MockedSubscription { - request: GraphQLRequest; -} - -export class MockLink extends ApolloLink { - public addTypename: Boolean = true; - private mockedResponsesByKey: { [key: string]: MockedResponse[] } = {}; - - constructor(mockedResponses: ReadonlyArray, addTypename: Boolean = true) { - super(); - this.addTypename = addTypename; - if (mockedResponses) - mockedResponses.forEach(mockedResponse => { - this.addMockedResponse(mockedResponse); - }); - } - - public addMockedResponse(mockedResponse: MockedResponse) { - const key = requestToKey(mockedResponse.request, this.addTypename); - let mockedResponses = this.mockedResponsesByKey[key]; - if (!mockedResponses) { - mockedResponses = []; - this.mockedResponsesByKey[key] = mockedResponses; - } - mockedResponses.push(mockedResponse); - } - - public request(operation: Operation) { - const key = requestToKey(operation, this.addTypename); - let responseIndex; - const response = (this.mockedResponsesByKey[key] || []).find((res, index) => { - const requestVariables = operation.variables || {}; - const mockedResponseVariables = res.request.variables || {}; - if (!isEqual(requestVariables, mockedResponseVariables)) { - return false; - } - responseIndex = index; - return true; - }); - - if (!response || typeof responseIndex === 'undefined') { - throw new Error( - `No more mocked responses for the query: ${print( - operation.query, - )}, variables: ${JSON.stringify(operation.variables)}`, - ); - } - - this.mockedResponsesByKey[key].splice(responseIndex, 1); - - const { result, error, delay, newData } = response; - - if (newData) { - response.result = newData(); - this.mockedResponsesByKey[key].push(response); - } - - if (!result && !error) { - throw new Error(`Mocked response should contain either result or error: ${key}`); - } - - return new Observable(observer => { - let timer = setTimeout(() => { - if (error) { - observer.error(error); - } else { - if (result) observer.next(result); - observer.complete(); - } - }, delay ? delay : 0); - - return () => { - clearTimeout(timer); - }; - }); - } -} - -export class MockSubscriptionLink extends ApolloLink { - // private observer: Observer; - public unsubscribers: any[] = []; - public setups: any[] = []; - - private observer: any; - - constructor() { - super(); - } - - public request(_req: any) { - return new Observable(observer => { - this.setups.forEach(x => x()); - this.observer = observer; - return () => { - this.unsubscribers.forEach(x => x()); - }; - }); - } - - public simulateResult(result: MockedSubscriptionResult) { - setTimeout(() => { - const { observer } = this; - if (!observer) throw new Error('subscription torn down'); - if (result.result && observer.next) observer.next(result.result); - if (result.error && observer.error) observer.error(result.error); - }, result.delay || 0); - } - - public onSetup(listener: any): void { - this.setups = this.setups.concat([listener]); - } - - public onUnsubscribe(listener: any): void { - this.unsubscribers = this.unsubscribers.concat([listener]); - } -} - -function requestToKey(request: GraphQLRequest, addTypename: Boolean): string { - const queryString = - request.query && print(addTypename ? addTypenameToDocument(request.query) : request.query); - - const requestKey = { query: queryString }; - - return JSON.stringify(requestKey); -} - -// Pass in multiple mocked responses, so that you can test flows that end up -// making multiple queries to the server -// NOTE: The last arg can optionally be an `addTypename` arg -export function mockSingleLink(...mockedResponses: Array): ApolloLink { - // to pull off the potential typename. If this isn't a boolean, we'll just set it true later - let maybeTypename = mockedResponses[mockedResponses.length - 1]; - let mocks = mockedResponses.slice(0, mockedResponses.length - 1); - - if (typeof maybeTypename !== 'boolean') { - mocks = mockedResponses; - maybeTypename = true; - } - - return new MockLink(mocks, maybeTypename); -} - -export function mockObservableLink(): MockSubscriptionLink { - return new MockSubscriptionLink(); -} +import { + Operation, + GraphQLRequest, + ApolloLink, + FetchResult, + Observable, + // Observer, +} from 'apollo-link'; + +import { print } from 'graphql/language/printer'; +import { addTypenameToDocument } from 'apollo-utilities'; +import isEqual from 'lodash.isequal'; + +export interface MockedResponse { + request: GraphQLRequest; + result?: FetchResult; + error?: Error; + delay?: number; + newData?: () => FetchResult; +} + +export interface MockedSubscriptionResult { + result?: FetchResult; + error?: Error; + delay?: number; +} + +export interface MockedSubscription { + request: GraphQLRequest; +} + +export class MockLink extends ApolloLink { + public addTypename: Boolean = true; + private mockedResponsesByKey: { [key: string]: MockedResponse[] } = {}; + + constructor(mockedResponses: ReadonlyArray, addTypename: Boolean = true) { + super(); + this.addTypename = addTypename; + if (mockedResponses) + mockedResponses.forEach(mockedResponse => { + this.addMockedResponse(mockedResponse); + }); + } + + public addMockedResponse(mockedResponse: MockedResponse) { + const key = requestToKey(mockedResponse.request, this.addTypename); + let mockedResponses = this.mockedResponsesByKey[key]; + if (!mockedResponses) { + mockedResponses = []; + this.mockedResponsesByKey[key] = mockedResponses; + } + mockedResponses.push(mockedResponse); + } + + public request(operation: Operation) { + const key = requestToKey(operation, this.addTypename); + let responseIndex; + const response = (this.mockedResponsesByKey[key] || []).find((res, index) => { + const requestVariables = operation.variables || {}; + const mockedResponseVariables = res.request.variables || {}; + if (!isEqual(requestVariables, mockedResponseVariables)) { + return false; + } + responseIndex = index; + return true; + }); + + if (!response || typeof responseIndex === 'undefined') { + throw new Error( + `No more mocked responses for the query: ${print( + operation.query, + )}, variables: ${JSON.stringify(operation.variables)}`, + ); + } + + this.mockedResponsesByKey[key].splice(responseIndex, 1); + + const { result, error, delay, newData } = response; + + if (newData) { + response.result = newData(); + this.mockedResponsesByKey[key].push(response); + } + + if (!result && !error) { + throw new Error(`Mocked response should contain either result or error: ${key}`); + } + + return new Observable(observer => { + let timer = setTimeout(() => { + if (error) { + observer.error(error); + } else { + if (result) observer.next(result); + observer.complete(); + } + }, delay ? delay : 0); + + return () => { + clearTimeout(timer); + }; + }); + } +} + +export class MockSubscriptionLink extends ApolloLink { + // private observer: Observer; + public unsubscribers: any[] = []; + public setups: any[] = []; + + private observer: any; + + constructor() { + super(); + } + + public request(_req: any) { + return new Observable(observer => { + this.setups.forEach(x => x()); + this.observer = observer; + return () => { + this.unsubscribers.forEach(x => x()); + }; + }); + } + + public simulateResult(result: MockedSubscriptionResult) { + setTimeout(() => { + const { observer } = this; + if (!observer) throw new Error('subscription torn down'); + if (result.result && observer.next) observer.next(result.result); + if (result.error && observer.error) observer.error(result.error); + }, result.delay || 0); + } + + public onSetup(listener: any): void { + this.setups = this.setups.concat([listener]); + } + + public onUnsubscribe(listener: any): void { + this.unsubscribers = this.unsubscribers.concat([listener]); + } +} + +function requestToKey(request: GraphQLRequest, addTypename: Boolean): string { + const queryString = + request.query && print(addTypename ? addTypenameToDocument(request.query) : request.query); + + const requestKey = { query: queryString }; + + return JSON.stringify(requestKey); +} + +// Pass in multiple mocked responses, so that you can test flows that end up +// making multiple queries to the server +// NOTE: The last arg can optionally be an `addTypename` arg +export function mockSingleLink(...mockedResponses: Array): ApolloLink { + // to pull off the potential typename. If this isn't a boolean, we'll just set it true later + let maybeTypename = mockedResponses[mockedResponses.length - 1]; + let mocks = mockedResponses.slice(0, mockedResponses.length - 1); + + if (typeof maybeTypename !== 'boolean') { + mocks = mockedResponses; + maybeTypename = true; + } + + return new MockLink(mocks, maybeTypename); +} + +export function mockObservableLink(): MockSubscriptionLink { + return new MockSubscriptionLink(); +} From 37b3ad55ba4c8fe61c0a1cded894bd8e563d62d3 Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Fri, 28 Sep 2018 09:54:02 -0400 Subject: [PATCH 4/4] Changelog update --- Changelog.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index b4e8bc2505..c0ca818211 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,8 +2,13 @@ ## vNext -- Fix `lodash` typings.
+- Fix `lodash` typings.
[@williamboman](https://github.com/williamboman) in [#2430](https://github.com/apollographql/react-apollo/pull/2430) +- Replace the `lodash` dependency with `lodash.flowright` (since that's the + only non-dev `lodash` function we're dependent on). Dev `lodash` + dependencies have also been updated to use their individual module + equivalent.
+ [@hwillson](https://github.com/hwillson) in [#2435](https://github.com/apollographql/react-apollo/pull/2435) ## 2.2.2 (September 28, 2018)