UNPKG

8.97 kBJavaScriptView Raw
1import { ApolloModule, Apollo } from 'apollo-angular';
2import { Observable, ApolloLink } from 'apollo-link';
3import { InMemoryCache } from 'apollo-cache-inmemory';
4import { ApolloCache } from 'apollo-cache';
5import { Injectable, InjectionToken, NgModule, Optional, Inject } from '@angular/core';
6import { print } from 'graphql';
7import { ApolloError } from 'apollo-client';
8
9/**
10 * Controller to be injected into tests, that allows for mocking and flushing
11 * of operations.
12 *
13 *
14 */
15class ApolloTestingController {
16}
17
18const isApolloError = (err) => err && err.hasOwnProperty('graphQLErrors');
19const ɵ0 = isApolloError;
20class TestOperation {
21 constructor(operation, observer) {
22 this.operation = operation;
23 this.observer = observer;
24 }
25 flush(result) {
26 if (isApolloError(result)) {
27 this.observer.error(result);
28 }
29 else {
30 this.observer.next(result);
31 this.observer.complete();
32 }
33 }
34 networkError(error) {
35 const apolloError = new ApolloError({
36 networkError: error,
37 });
38 this.flush(apolloError);
39 }
40 graphqlErrors(errors) {
41 this.flush({
42 errors,
43 });
44 }
45}
46
47/**
48 * A testing backend for `Apollo`.
49 *
50 * `ApolloTestingBackend` works by keeping a list of all open operations.
51 * As operations come in, they're added to the list. Users can assert that specific
52 * operations were made and then flush them. In the end, a verify() method asserts
53 * that no unexpected operations were made.
54 */
55class ApolloTestingBackend {
56 constructor() {
57 /**
58 * List of pending operations which have not yet been expected.
59 */
60 this.open = [];
61 }
62 /**
63 * Handle an incoming operation by queueing it in the list of open operations.
64 */
65 handle(op) {
66 return new Observable((observer) => {
67 const testOp = new TestOperation(op, observer);
68 this.open.push(testOp);
69 });
70 }
71 /**
72 * Helper function to search for operations in the list of open operations.
73 */
74 _match(match) {
75 if (typeof match === 'string') {
76 return this.open.filter(testOp => testOp.operation.operationName === match);
77 }
78 else if (typeof match === 'function') {
79 return this.open.filter(testOp => match(testOp.operation));
80 }
81 else {
82 if (this.isDocumentNode(match)) {
83 return this.open.filter(testOp => print(testOp.operation.query) === print(match));
84 }
85 return this.open.filter(testOp => this.matchOp(match, testOp));
86 }
87 }
88 matchOp(match, testOp) {
89 const variables = JSON.stringify(match.variables);
90 const extensions = JSON.stringify(match.extensions);
91 const sameName = this.compare(match.operationName, testOp.operation.operationName);
92 const sameVariables = this.compare(variables, testOp.operation.variables);
93 const sameQuery = print(testOp.operation.query) === print(match.query);
94 const sameExtensions = this.compare(extensions, testOp.operation.extensions);
95 return sameName && sameVariables && sameQuery && sameExtensions;
96 }
97 compare(expected, value) {
98 const prepare = (val) => typeof val === 'string' ? val : JSON.stringify(val);
99 const received = prepare(value);
100 return !expected || received === expected;
101 }
102 /**
103 * Search for operations in the list of open operations, and return all that match
104 * without asserting anything about the number of matches.
105 */
106 match(match) {
107 const results = this._match(match);
108 results.forEach(result => {
109 const index = this.open.indexOf(result);
110 if (index !== -1) {
111 this.open.splice(index, 1);
112 }
113 });
114 return results;
115 }
116 /**
117 * Expect that a single outstanding request matches the given matcher, and return
118 * it.
119 *
120 * operations returned through this API will no longer be in the list of open operations,
121 * and thus will not match twice.
122 */
123 expectOne(match, description) {
124 description = description || this.descriptionFromMatcher(match);
125 const matches = this.match(match);
126 if (matches.length > 1) {
127 throw new Error(`Expected one matching operation for criteria "${description}", found ${matches.length} operations.`);
128 }
129 if (matches.length === 0) {
130 throw new Error(`Expected one matching operation for criteria "${description}", found none.`);
131 }
132 return matches[0];
133 }
134 /**
135 * Expect that no outstanding operations match the given matcher, and throw an error
136 * if any do.
137 */
138 expectNone(match, description) {
139 description = description || this.descriptionFromMatcher(match);
140 const matches = this.match(match);
141 if (matches.length > 0) {
142 throw new Error(`Expected zero matching operations for criteria "${description}", found ${matches.length}.`);
143 }
144 }
145 /**
146 * Validate that there are no outstanding operations.
147 */
148 verify() {
149 const open = this.open;
150 if (open.length > 0) {
151 // Show the methods and URLs of open operations in the error, for convenience.
152 const operations = open
153 .map(testOp => testOp.operation.operationName)
154 .join(', ');
155 throw new Error(`Expected no open operations, found ${open.length}: ${operations}`);
156 }
157 }
158 isDocumentNode(docOrOp) {
159 return !docOrOp.operationName;
160 }
161 descriptionFromMatcher(matcher) {
162 if (typeof matcher === 'string') {
163 return `Match operationName: ${matcher}`;
164 }
165 else if (typeof matcher === 'object') {
166 if (this.isDocumentNode(matcher)) {
167 return `Match DocumentNode`;
168 }
169 const name = matcher.operationName || '(any)';
170 const variables = JSON.stringify(matcher.variables) || '(any)';
171 return `Match operation: ${name}, variables: ${variables}`;
172 }
173 else {
174 return `Match by function: ${matcher.name}`;
175 }
176 }
177}
178ApolloTestingBackend.decorators = [
179 { type: Injectable }
180];
181
182const APOLLO_TESTING_CACHE = new InjectionToken('apollo-angular/testing cache');
183const APOLLO_TESTING_NAMED_CACHE = new InjectionToken('apollo-angular/testing named cache');
184const APOLLO_TESTING_CLIENTS = new InjectionToken('apollo-angular/testing named clients');
185function addClient(name, op) {
186 op.clientName = name;
187 return op;
188}
189class ApolloTestingModuleCore {
190 constructor(apollo, backend, namedClients, cache, namedCaches) {
191 function createOptions(name, c) {
192 return {
193 link: new ApolloLink((operation) => backend.handle(addClient(name, operation))),
194 cache: c ||
195 new InMemoryCache({
196 addTypename: false,
197 }),
198 };
199 }
200 apollo.create(createOptions('default', cache));
201 if (namedClients && namedClients.length) {
202 namedClients.forEach((name) => {
203 const caches = namedCaches && typeof namedCaches === 'object' ? namedCaches : {};
204 apollo.createNamed(name, createOptions(name, caches[name]));
205 });
206 }
207 }
208}
209ApolloTestingModuleCore.decorators = [
210 { type: NgModule, args: [{
211 imports: [ApolloModule],
212 providers: [
213 ApolloTestingBackend,
214 { provide: ApolloTestingController, useExisting: ApolloTestingBackend },
215 ],
216 },] }
217];
218ApolloTestingModuleCore.ctorParameters = () => [
219 { type: Apollo },
220 { type: ApolloTestingBackend },
221 { type: Array, decorators: [{ type: Optional }, { type: Inject, args: [APOLLO_TESTING_CLIENTS,] }] },
222 { type: ApolloCache, decorators: [{ type: Optional }, { type: Inject, args: [APOLLO_TESTING_CACHE,] }] },
223 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [APOLLO_TESTING_NAMED_CACHE,] }] }
224];
225class ApolloTestingModule {
226 static withClients(names) {
227 return {
228 ngModule: ApolloTestingModuleCore,
229 providers: [
230 {
231 provide: APOLLO_TESTING_CLIENTS,
232 useValue: names,
233 },
234 ],
235 };
236 }
237}
238ApolloTestingModule.decorators = [
239 { type: NgModule, args: [{
240 imports: [ApolloTestingModuleCore],
241 },] }
242];
243
244/**
245 * Generated bundle index. Do not edit.
246 */
247
248export { APOLLO_TESTING_CACHE, APOLLO_TESTING_NAMED_CACHE, ApolloTestingController, ApolloTestingModule, TestOperation, APOLLO_TESTING_CLIENTS as ɵa, ApolloTestingModuleCore as ɵb, ApolloTestingBackend as ɵc };
249//# sourceMappingURL=ngApolloTesting.js.map