1 | (function setupNgDescribe(root) {
|
2 |
|
3 |
|
4 | la(check.object(root), 'missing root');
|
5 |
|
6 | var _defaults = {
|
7 |
|
8 | name: 'default tests',
|
9 | modules: [],
|
10 | configs: {},
|
11 | inject: [],
|
12 | exposeApi: false,
|
13 | tests: function () {},
|
14 | mocks: {},
|
15 | helpful: false,
|
16 | controllers: [],
|
17 | element: '',
|
18 | http: {},
|
19 |
|
20 | only: false,
|
21 | verbose: false,
|
22 | skip: false,
|
23 | parentScope: {}
|
24 | };
|
25 |
|
26 | function defaults(opts) {
|
27 | opts = opts || {};
|
28 | return angular.extend(angular.copy(_defaults), opts);
|
29 | }
|
30 |
|
31 | var ngDescribeSchema = {
|
32 |
|
33 | name: check.unemptyString,
|
34 | modules: check.arrayOfStrings,
|
35 | configs: check.object,
|
36 | inject: check.arrayOfStrings,
|
37 | exposeApi: check.bool,
|
38 | tests: check.fn,
|
39 | mocks: check.object,
|
40 | helpful: check.bool,
|
41 | controllers: check.arrayOfStrings,
|
42 | element: check.string,
|
43 |
|
44 |
|
45 |
|
46 | only: check.bool,
|
47 | verbose: check.bool,
|
48 | skip: check.or(check.bool, check.unemptyString),
|
49 | parentScope: check.object
|
50 | };
|
51 |
|
52 | function uniq(a) {
|
53 | var seen = {};
|
54 | return a.filter(function(item) {
|
55 | return seen.hasOwnProperty(item) ? false : (seen[item] = true);
|
56 | });
|
57 | }
|
58 |
|
59 | function clone(a) {
|
60 | return JSON.parse(JSON.stringify(a));
|
61 | }
|
62 |
|
63 | function methodNames(reference) {
|
64 | la(check.object(reference), 'expected object reference, not', reference);
|
65 |
|
66 | return Object.keys(reference).filter(function (key) {
|
67 | return check.fn(reference[key]);
|
68 | });
|
69 | }
|
70 |
|
71 | function copyAliases(options) {
|
72 | if (options.config && !options.configs) {
|
73 | options.configs = options.config;
|
74 | }
|
75 | if (options.mock && !options.mocks) {
|
76 | options.mocks = options.mock;
|
77 | }
|
78 | if (options.module && !options.modules) {
|
79 | options.modules = options.module;
|
80 | }
|
81 | if (options.test && !options.tests) {
|
82 | options.tests = options.test;
|
83 | }
|
84 | if (options.controller && !options.controllers) {
|
85 | options.controllers = options.controller;
|
86 | }
|
87 | return options;
|
88 | }
|
89 |
|
90 | function ensureArrays(options) {
|
91 | if (check.string(options.modules)) {
|
92 | options.modules = [options.modules];
|
93 | }
|
94 | if (check.string(options.inject)) {
|
95 | options.inject = [options.inject];
|
96 | }
|
97 | if (check.string(options.controllers)) {
|
98 | options.controllers = [options.controllers];
|
99 | }
|
100 | return options;
|
101 | }
|
102 |
|
103 | function collectInjects(options) {
|
104 | la(check.object(options) && check.array(options.controllers),
|
105 | 'missing controllers', options);
|
106 |
|
107 | if (options.controllers.length || options.exposeApi) {
|
108 | options.inject.push('$controller');
|
109 | options.inject.push('$rootScope');
|
110 | }
|
111 |
|
112 | if (check.unemptyString(options.element) || options.exposeApi) {
|
113 | options.inject.push('$rootScope');
|
114 | options.inject.push('$compile');
|
115 | }
|
116 |
|
117 | if (check.not.empty(options.http) || check.fn(options.http)) {
|
118 | options.inject.push('$httpBackend');
|
119 | }
|
120 |
|
121 |
|
122 | options.modules = options.modules.concat(Object.keys(options.mocks));
|
123 |
|
124 | options.modules = options.modules.concat(Object.keys(options.configs));
|
125 |
|
126 | return options;
|
127 | }
|
128 |
|
129 | function ensureUnique(options) {
|
130 | options.inject = uniq(options.inject);
|
131 | options.modules = uniq(options.modules);
|
132 | options.controllers = uniq(options.controllers);
|
133 | return options;
|
134 | }
|
135 |
|
136 | function decideSuiteFunction(options) {
|
137 | var suiteFn = root.describe;
|
138 | if (options.only) {
|
139 |
|
140 |
|
141 |
|
142 | suiteFn = root.fdescribe || root.ddescribe || root.describe.only;
|
143 | }
|
144 | if (options.helpful) {
|
145 | suiteFn = root.helpDescribe;
|
146 | }
|
147 | if (options.skip) {
|
148 | la(!options.only, 'skip and only are exclusive options', options);
|
149 | suiteFn = root.xdescribe || root.describe.skip;
|
150 | }
|
151 | return suiteFn;
|
152 | }
|
153 |
|
154 | function decideLogFunction(options) {
|
155 | return options.verbose ? angular.bind(console, console.log) : angular.noop;
|
156 | }
|
157 |
|
158 | function ngDescribe(options) {
|
159 | la(check.object(options), 'expected options object, see docs', options);
|
160 | la(check.defined(angular), 'missing angular');
|
161 |
|
162 | options = copyAliases(options);
|
163 | options = defaults(options);
|
164 | options = ensureArrays(options);
|
165 | options = collectInjects(options);
|
166 | options = ensureUnique(options);
|
167 |
|
168 | var log = decideLogFunction(options);
|
169 | la(check.fn(log), 'could not decide on log function', options);
|
170 |
|
171 | var isValidNgDescribe = angular.bind(null, check.schema, ngDescribeSchema);
|
172 | la(isValidNgDescribe(options), 'invalid input options', options);
|
173 |
|
174 | var suiteFn = decideSuiteFunction(options);
|
175 | la(check.fn(suiteFn), 'missing describe function', options);
|
176 |
|
177 |
|
178 | var mockInjects = [];
|
179 |
|
180 | var aliasedDependencies = {
|
181 | '$httpBackend': 'http'
|
182 | };
|
183 |
|
184 | function ngSpecs() {
|
185 |
|
186 | var dependencies = {};
|
187 |
|
188 | function partiallInjectMethod(owner, mockName, fn, $injector) {
|
189 | la(check.unemptyString(mockName), 'expected mock name', mockName);
|
190 | la(check.fn(fn), 'expected function for', mockName, 'got', fn);
|
191 |
|
192 | var diNames = $injector.annotate(fn);
|
193 | log('dinames for', mockName, diNames);
|
194 | mockInjects.push.apply(mockInjects, diNames);
|
195 |
|
196 | var wrappedFunction = function injectedDependenciesIntoMockFunction() {
|
197 | var runtimeArguments = arguments;
|
198 | var k = 0;
|
199 | var args = diNames.map(function (name) {
|
200 | if (check.has(dependencies, name)) {
|
201 |
|
202 | return dependencies[name];
|
203 | }
|
204 |
|
205 | return runtimeArguments[k++];
|
206 | });
|
207 | return fn.apply(owner, args);
|
208 | };
|
209 | return wrappedFunction;
|
210 | }
|
211 |
|
212 | function partiallyInjectObject(reference, mockName, $injector) {
|
213 | la(check.object(reference), 'expected object reference, not', reference);
|
214 |
|
215 | methodNames(reference).forEach(function (key) {
|
216 | reference[key] = partiallInjectMethod(reference,
|
217 | mockName + '.' + key, reference[key], $injector);
|
218 | });
|
219 |
|
220 | return reference;
|
221 | }
|
222 |
|
223 | root.beforeEach(function mockModules() {
|
224 | log('ngDescribe', options.name);
|
225 | log('loading modules', options.modules);
|
226 |
|
227 | options.modules.forEach(function loadAngularModules(moduleName) {
|
228 | if (options.configs[moduleName]) {
|
229 | var m = angular.module(moduleName);
|
230 | m.config([moduleName + 'Provider', function (provider) {
|
231 | var cloned = clone(options.configs[moduleName]);
|
232 | log('setting config', moduleName + 'Provider to', cloned);
|
233 | provider.set(cloned);
|
234 | }]);
|
235 | } else {
|
236 | angular.mock.module(moduleName, function ($provide, $injector) {
|
237 | var mocks = options.mocks[moduleName];
|
238 | if (mocks) {
|
239 | log('mocking', Object.keys(mocks));
|
240 | Object.keys(mocks).forEach(function (mockName) {
|
241 | var value = mocks[mockName];
|
242 |
|
243 | if (check.fn(value) && !value.injected) {
|
244 | value = partiallInjectMethod(mocks, mockName, value, $injector);
|
245 | value.injected = true;
|
246 | } else if (check.object(value) && !value.injected) {
|
247 | value = partiallyInjectObject(value, mockName, $injector);
|
248 | value.injected = true;
|
249 | }
|
250 |
|
251 | $provide.constant(mockName, value);
|
252 | });
|
253 | }
|
254 | });
|
255 | }
|
256 | });
|
257 | });
|
258 |
|
259 | function injectDependencies($injector) {
|
260 | log('injecting', options.inject);
|
261 |
|
262 | options.inject.forEach(function (dependencyName) {
|
263 | var injectedUnderName = aliasedDependencies[dependencyName] || dependencyName;
|
264 | la(check.unemptyString(injectedUnderName),
|
265 | 'could not rename dependency', dependencyName);
|
266 | dependencies[injectedUnderName] =
|
267 | dependencies[dependencyName] = $injector.get(dependencyName);
|
268 | });
|
269 |
|
270 | mockInjects = uniq(mockInjects);
|
271 | log('injecting existing dependencies for mocks', mockInjects);
|
272 | mockInjects.forEach(function (dependencyName) {
|
273 | if ($injector.has(dependencyName)) {
|
274 | dependencies[dependencyName] = $injector.get(dependencyName);
|
275 | }
|
276 | });
|
277 | }
|
278 |
|
279 | function setupControllers() {
|
280 | log('setting up controllers', options.controllers);
|
281 | options.controllers.forEach(function (controllerName) {
|
282 | la(check.fn(dependencies.$controller), 'need $controller service', dependencies);
|
283 | la(check.object(dependencies.$rootScope), 'need $rootScope service', dependencies);
|
284 | var scope = dependencies.$rootScope.$new();
|
285 | dependencies.$controller(controllerName, {
|
286 | $scope: scope
|
287 | });
|
288 | dependencies[controllerName] = scope;
|
289 | });
|
290 | }
|
291 |
|
292 | function isResponseCode(x) {
|
293 | return check.number(x) && x >= 200 && x < 550;
|
294 | }
|
295 |
|
296 | function isResponsePair(x) {
|
297 | return check.array(x) &&
|
298 | x.length === 2 &&
|
299 | isResponseCode(x[0]);
|
300 | }
|
301 |
|
302 | function setupMethodHttpResponses(methodName) {
|
303 | la(check.unemptyString(methodName), 'expected method name', methodName);
|
304 | var mockConfig = options.http[methodName];
|
305 |
|
306 | if (check.fn(mockConfig)) {
|
307 | mockConfig = mockConfig();
|
308 | }
|
309 |
|
310 | la(check.object(mockConfig),
|
311 | 'expected mock config for http method', methodName, mockConfig);
|
312 | var method = methodName.toUpperCase();
|
313 |
|
314 | Object.keys(mockConfig).forEach(function (url) {
|
315 | log('mocking', method, 'response for url', url);
|
316 |
|
317 | var value = mockConfig[url];
|
318 | if (check.fn(value)) {
|
319 | return dependencies.http.when(method, url).respond(function () {
|
320 | var result = value.apply(null, arguments);
|
321 | if (isResponsePair(result)) {
|
322 | return result;
|
323 | }
|
324 | return [200, result];
|
325 | });
|
326 | }
|
327 | if (check.number(value) && isResponseCode(value)) {
|
328 | return dependencies.http.when(method, url).respond(value);
|
329 | }
|
330 | if (isResponsePair(value)) {
|
331 | return dependencies.http.when(method, url).respond(value[0], value[1]);
|
332 | }
|
333 | return dependencies.http.when(method, url).respond(200, value);
|
334 | });
|
335 | }
|
336 |
|
337 | function setupHttpResponses() {
|
338 | if (check.not.has(options, 'http')) {
|
339 | return;
|
340 | }
|
341 | if (check.empty(options.http)) {
|
342 | return;
|
343 | }
|
344 |
|
345 | la(check.object(options.http), 'expected mock http object', options.http);
|
346 |
|
347 | log('setting up mock http responses', options.http);
|
348 | la(check.has(dependencies, 'http'), 'expected to inject http', dependencies);
|
349 |
|
350 | function hasMockResponses(methodName) {
|
351 | return check.has(options.http, methodName);
|
352 | }
|
353 |
|
354 | var validMethods = ['get', 'head', 'post', 'put', 'delete', 'jsonp', 'patch'];
|
355 | validMethods
|
356 | .filter(hasMockResponses)
|
357 | .forEach(setupMethodHttpResponses);
|
358 | }
|
359 |
|
360 | function setupDigestcycleShortcut() {
|
361 | if (dependencies.$httpBackend ||
|
362 | dependencies.http ||
|
363 | dependencies.$rootScope) {
|
364 | dependencies.step = function step() {
|
365 | if (dependencies.http && check.fn(dependencies.http.flush)) {
|
366 | dependencies.http.flush();
|
367 | }
|
368 | if (dependencies.$rootScope) {
|
369 | dependencies.$rootScope.$digest();
|
370 | }
|
371 | };
|
372 | } else {
|
373 | dependencies.step = null;
|
374 | }
|
375 | }
|
376 |
|
377 |
|
378 | root.beforeEach(function loadDynamicHttp() {
|
379 | if (check.fn(options.http)) {
|
380 | options.http = options.http();
|
381 | console.log('http function returned', options.http);
|
382 | }
|
383 | });
|
384 |
|
385 | root.beforeEach(angular.mock.inject(injectDependencies));
|
386 | root.beforeEach(setupDigestcycleShortcut);
|
387 | root.beforeEach(setupControllers);
|
388 | root.beforeEach(setupHttpResponses);
|
389 |
|
390 | function setupElement(elementHtml) {
|
391 | la(check.fn(dependencies.$compile), 'missing $compile', dependencies);
|
392 |
|
393 | var scope = dependencies.$rootScope.$new();
|
394 | angular.extend(scope, angular.copy(options.parentScope));
|
395 | log('created element scope with values', options.parentScope);
|
396 |
|
397 | var element = angular.element(elementHtml);
|
398 | var compiled = dependencies.$compile(element);
|
399 | compiled(scope);
|
400 | dependencies.$rootScope.$digest();
|
401 |
|
402 | dependencies.element = element;
|
403 | dependencies.parentScope = scope;
|
404 | }
|
405 |
|
406 | function exposeApi() {
|
407 | return {
|
408 | setupElement: setupElement
|
409 | };
|
410 | }
|
411 |
|
412 | var toExpose = options.exposeApi ? exposeApi() : undefined;
|
413 | options.tests(dependencies, toExpose);
|
414 |
|
415 |
|
416 |
|
417 | if (check.unemptyString(options.element)) {
|
418 | log('setting up element', options.element);
|
419 | root.beforeEach(function () {
|
420 | setupElement(options.element);
|
421 | });
|
422 | root.afterEach(function () {
|
423 | delete dependencies.element;
|
424 | });
|
425 | }
|
426 |
|
427 | function deleteDependencies() {
|
428 | options.inject.forEach(function (dependencyName) {
|
429 | la(check.unemptyString(dependencyName), 'missing dependency name', dependencyName);
|
430 | var name = aliasedDependencies[dependencyName] || dependencyName;
|
431 | la(check.has(dependencies, name),
|
432 | 'cannot find injected dependency', name, 'for', dependencyName);
|
433 | la(check.has(dependencies, dependencyName),
|
434 | 'cannot find injected dependency', dependencyName);
|
435 | delete dependencies[name];
|
436 | delete dependencies[dependencyName];
|
437 | });
|
438 | }
|
439 | root.afterEach(deleteDependencies);
|
440 | }
|
441 |
|
442 | suiteFn(options.name, ngSpecs);
|
443 | }
|
444 |
|
445 | root.ngDescribe = ngDescribe;
|
446 |
|
447 | }(this));
|