-
Notifications
You must be signed in to change notification settings - Fork 8.3k
/
Copy pathprivate.js
190 lines (165 loc) · 5.28 KB
/
private.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
import _ from 'lodash';
import uiModules from 'ui/modules';
/**
* # `Private()`
* Private module loader, used to merge angular and require js dependency styles
* by allowing a require.js module to export a single provider function that will
* create a value used within an angular application. This provider can declare
* angular dependencies by listing them as arguments, and can be require additional
* Private modules.
*
* ## Define a private module provider:
* ```js
* define(function (require) {
* return function PingProvider($http) {
* this.ping = function () {
* return $http.head('/health-check');
* };
* };
* });
* ```
*
* ## Require a private module:
* ```js
* define(function (require) {
* return function ServerHealthProvider(Private, Promise) {
* let ping = Private(require('ui/ping'));
* return {
* check: Promise.method(function () {
* let attempts = 0;
* return (function attempt() {
* attempts += 1;
* return ping.ping()
* .catch(function (err) {
* if (attempts < 3) return attempt();
* })
* }())
* .then(function () {
* return true;
* })
* .catch(function () {
* return false;
* });
* })
* }
* };
* });
* ```
*
* # `Private.stub(provider, newInstance)`
* `Private.stub()` replaces the instance of a module with another value. This is all we have needed until now.
*
* ```js
* beforeEach(inject(function ($injector, Private) {
* Private.stub(
* // since this module just exports a function, we need to change
* // what Private returns in order to modify it's behavior
* require('ui/agg_response/hierarchical/_build_split'),
* sinon.stub().returns(fakeSplit)
* );
* }));
* ```
*
* # `Private.swap(oldProvider, newProvider)`
* This new method does an 1-for-1 swap of module providers, unlike `stub()` which replaces a modules instance.
* Pass the module you want to swap out, and the one it should be replaced with, then profit.
*
* Note: even though this example shows `swap()` being called in a config
* function, it can be called from anywhere. It is particularly useful
* in this scenario though.
*
* ```js
* beforeEach(module('kibana', function (PrivateProvider) {
* PrivateProvider.swap(
* // since the courier is required automatically before the tests are loaded,
* // we can't stub it's internal components unless we do so before the
* // application starts. This is why angular has config functions
* require('ui/courier/_redirect_when_missing'),
* function StubbedRedirectProvider($decorate) {
* // $decorate is a function that will instantiate the original module when called
* return sinon.spy($decorate());
* }
* );
* }));
* ```
*
* @param {[type]} prov [description]
*/
let nextId = _.partial(_.uniqueId, 'privateProvider#');
function name(fn) {
return fn.name || fn.toString().split('\n').shift();
}
uiModules.get('kibana')
.provider('Private', function () {
let provider = this;
// one cache/swaps per Provider
let cache = {};
let swaps = {};
// return the uniq id for this function
function identify(fn) {
if (typeof fn !== 'function') {
throw new TypeError('Expected private module "' + fn + '" to be a function');
}
if (fn.$$id) return fn.$$id;
else return (fn.$$id = nextId());
}
provider.stub = function (fn, instance) {
cache[identify(fn)] = instance;
return instance;
};
provider.swap = function (fn, prov) {
let id = identify(fn);
swaps[id] = prov;
};
provider.$get = ['$injector', function PrivateFactory($injector) {
// prevent circular deps by tracking where we came from
let privPath = [];
let pathToString = function () {
return privPath.map(name).join(' -> ');
};
// call a private provider and return the instance it creates
function instantiate(prov, locals) {
if (~privPath.indexOf(prov)) {
throw new Error(
'Circular refrence to "' + name(prov) + '"' +
' found while resolving private deps: ' + pathToString()
);
}
privPath.push(prov);
let context = {};
let instance = $injector.invoke(prov, context, locals);
if (!_.isObject(instance)) instance = context;
privPath.pop();
return instance;
}
// retrieve an instance from cache or create and store on
function get(id, prov, $delegateId, $delegateProv) {
if (cache[id]) return cache[id];
let instance;
if ($delegateId != null && $delegateProv != null) {
instance = instantiate(prov, {
$decorate: _.partial(get, $delegateId, $delegateProv)
});
} else {
instance = instantiate(prov);
}
return (cache[id] = instance);
}
// main api, get the appropriate instance for a provider
function Private(prov) {
let id = identify(prov);
let $delegateId;
let $delegateProv;
if (swaps[id]) {
$delegateId = id;
$delegateProv = prov;
prov = swaps[$delegateId];
id = identify(prov);
}
return get(id, prov, $delegateId, $delegateProv);
}
Private.stub = provider.stub;
Private.swap = provider.swap;
return Private;
}];
});