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)
diff --git a/dangerfile.ts b/dangerfile.ts
index bbee14f245..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/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..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';
-
-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();
+}
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..1226f32199 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', () => {