Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit 4d6233e

Browse files
committed
feat(Http): UrlRewriter, HttpBackend for better tests.
1 parent 579d614 commit 4d6233e

File tree

6 files changed

+291
-8
lines changed

6 files changed

+291
-8
lines changed

lib/angular.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ class AngularModule extends Module {
128128
type(Interpolate, Interpolate);
129129
type(CacheFactory, CacheFactory);
130130
type(Http, Http);
131+
type(UrlRewriter, UrlRewriter);
132+
type(HttpFutures, HttpFutures);
133+
type(HttpBackend, HttpBackend);
131134
type(BlockCache, BlockCache);
132135
type(TemplateCache, TemplateCache);
133136

lib/block.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ class Block implements ElementWrapper {
108108
if (ref.directive.isComponent) {
109109
//nodeModule.factory(type, new ComponentFactory(node, ref.directive), visibility: visibility);
110110
// TODO(misko): there should be no need to wrap function like this.
111-
nodeModule.factory(type, (Injector injector, Compiler compiler, Scope scope, Parser parser, BlockCache $blockCache) =>
112-
(new ComponentFactory(node, ref.directive))(injector, compiler, scope, parser, $blockCache),
111+
nodeModule.factory(type, (Injector injector, Compiler compiler, Scope scope, Parser parser, BlockCache $blockCache, UrlRewriter urlRewriter) =>
112+
(new ComponentFactory(node, ref.directive))(injector, compiler, scope, parser, $blockCache, urlRewriter),
113113
visibility: visibility);
114114
} else {
115115
nodeModule.type(type, type, visibility: visibility);
@@ -267,13 +267,13 @@ class ComponentFactory {
267267
ComponentFactory(this.element, this.directive);
268268

269269
dynamic call(Injector injector, Compiler compiler, Scope scope,
270-
Parser parser, BlockCache $blockCache) {
270+
Parser parser, BlockCache $blockCache, UrlRewriter urlRewriter) {
271271
this.compiler = compiler;
272272
shadowDom = element.createShadowRoot();
273273
shadowScope = scope.$new(true);
274274
createAttributeMapping(scope, shadowScope, parser);
275275
if (directive.$cssUrl != null) {
276-
shadowDom.innerHtml = '<style>@import "${directive.$cssUrl}"</style>';
276+
shadowDom.innerHtml = '<style>@import "${urlRewriter(directive.$cssUrl)}"</style>';
277277
}
278278
TemplateLoader templateLoader;
279279
if (directive.$template != null) {

lib/http.dart

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,41 @@
11
part of angular;
22

3+
// NOTE(deboer): This should be a generic utility class, but lets make sure
4+
// it works in this case first!
5+
class HttpFutures {
6+
value(x) => new async.Future.value(x);
7+
}
8+
9+
class UrlRewriter {
10+
String call(url) => url;
11+
}
12+
13+
class HttpBackend {
14+
getString(String rawUrl, {bool withCredentials, void onProgress(dom.ProgressEvent e)}) {
15+
return dom.HttpRequest.getString(url, withCredentials: withCredentials, onProgress: onProgress);
16+
}
17+
}
18+
319
class Http {
420
Map<String, async.Future<String>> _pendingRequests = <String, async.Future<String>>{};
5-
6-
async.Future<String> getString(String url, {bool withCredentials, void onProgress(dom.ProgressEvent e), Cache cache}) {
21+
UrlRewriter rewriter;
22+
HttpBackend backend;
23+
HttpFutures futures;
24+
25+
Http(UrlRewriter this.rewriter, HttpBackend this.backend, HttpFutures this.futures);
26+
27+
async.Future<String> getString(String rawUrl, {bool withCredentials, void onProgress(dom.ProgressEvent e), Cache cache}) {
28+
String url = rewriter(rawUrl);
29+
730
// We return a pending request only if caching is enabled.
831
if (cache != null && _pendingRequests.containsKey(url)) {
932
return _pendingRequests[url];
1033
}
1134
var cachedValue = cache != null ? cache.get(url) : null;
1235
if (cachedValue != null) {
13-
return new async.Future.value(cachedValue);
36+
return futures.value(cachedValue);
1437
}
15-
var result = dom.HttpRequest.getString(url, withCredentials: withCredentials, onProgress: onProgress).then((value) {
38+
var result = backend.getString(url, withCredentials: withCredentials, onProgress: onProgress).then((value) {
1639
if (cache != null) {
1740
cache.put(url, value);
1841
}

test/_http.dart

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ class MockHttp extends Http {
99
Map<String, MockHttpData> gets = {};
1010
List futures = [];
1111

12+
MockHttp(UrlRewriter rewriter, HttpBackend backend, HttpFutures futures) : super(rewriter, backend, futures);
13+
1214
expectGET(String url, String content, {int times: 1}) {
1315
gets[url] = new MockHttpData(content, times);
1416
}
@@ -46,4 +48,51 @@ class MockHttpData {
4648
toString() => value;
4749
}
4850

51+
class MockHttpFutures extends HttpFutures {
52+
List completersAndValues = [];
53+
Future value(x) {
54+
var completer = new Completer.sync();
55+
completersAndValues.add([completer, x]);
56+
return completer.future;
57+
}
58+
59+
trigger() {
60+
completersAndValues.forEach((cv) => cv[0].complete(cv[1]));
61+
completersAndValues = [];
62+
}
63+
}
64+
65+
class MockHttpBackend extends HttpBackend {
66+
Map<String, MockHttpData> gets = {};
67+
List completersAndValues = [];
68+
69+
expectGET(String url, String content, {int times: 1}) {
70+
gets[url] = new MockHttpData(content, times);
71+
}
72+
73+
flush() {
74+
completersAndValues.forEach((cv) => cv[0].complete(cv[1]));
75+
completersAndValues = [];
76+
}
77+
78+
assertAllGetsCalled() {
79+
if (gets.length != 0) {
80+
throw "Expected GETs not called $gets";
81+
}
82+
}
83+
84+
getString(String url, {bool withCredentials, void onProgress(ProgressEvent e)}) {
85+
if (!gets.containsKey(url)) throw "Unexpected URL $url $gets";
86+
var data = gets[url];
87+
data.times--;
88+
if (data.times <= 0) {
89+
gets.remove(url);
90+
}
91+
var expectedValue = data.value;
92+
var completer = new Completer.sync();
93+
completersAndValues.add([completer, expectedValue]);
94+
return completer.future;
95+
}
96+
}
97+
4998
main() {}

test/http_spec.dart

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import "_specs.dart";
2+
import "_http.dart";
3+
4+
var VALUE = 'val';
5+
var CACHED_VALUE = 'cached_value';
6+
7+
8+
class FakeCache implements Cache {
9+
get(x) => x == 'f' ? CACHED_VALUE : null;
10+
put(_,__) => null;
11+
12+
}
13+
14+
class SubstringRewriter extends UrlRewriter {
15+
call(String x) => x.substring(0, 1);
16+
}
17+
18+
main() {
19+
describe('http rewriting', () {
20+
var rewriter, futures, backend, cache;
21+
beforeEach(() {
22+
rewriter = new SubstringRewriter();
23+
futures = new MockHttpFutures();
24+
backend = new MockHttpBackend();
25+
cache = new FakeCache();
26+
});
27+
28+
it('should rewrite URLs before calling the backend', () {
29+
backend.expectGET('a', VALUE, times: 1);
30+
31+
var http = new Http(rewriter, backend, futures);
32+
var called = 0;
33+
http.getString('a[not sent to backed]').then((v) {
34+
expect(v).toBe(VALUE);
35+
called += 1;
36+
});
37+
38+
expect(called).toEqual(0);
39+
40+
backend.flush();
41+
42+
expect(called).toEqual(1);
43+
backend.assertAllGetsCalled();
44+
});
45+
46+
it('should support pending requests for different raw URLs', () {
47+
backend.expectGET('a', VALUE, times: 1);
48+
49+
var http = new Http(rewriter, backend, futures);
50+
var called = 0;
51+
http.getString('a[some string]', cache: cache).then((v) {
52+
expect(v).toBe(VALUE);
53+
called += 1;
54+
});
55+
http.getString('a[different string]', cache: cache).then((v) {
56+
expect(v).toBe(VALUE);
57+
called += 10;
58+
});
59+
60+
expect(called).toEqual(0);
61+
backend.flush();
62+
expect(called).toEqual(11);
63+
backend.assertAllGetsCalled();
64+
});
65+
66+
it('should support caching', () {
67+
var http = new Http(rewriter, backend, futures);
68+
var called = 0;
69+
http.getString('fromCache', cache: cache).then((v) {
70+
expect(v).toBe(CACHED_VALUE);
71+
called += 1;
72+
});
73+
74+
expect(called).toEqual(0);
75+
backend.flush();
76+
futures.trigger();
77+
78+
expect(called).toEqual(1);
79+
backend.assertAllGetsCalled();
80+
});
81+
});
82+
83+
describe('http caching', () {
84+
var rewriter, futures, backend, cache;
85+
beforeEach(() {
86+
rewriter = new UrlRewriter();
87+
futures = new MockHttpFutures();
88+
backend = new MockHttpBackend();
89+
cache = new FakeCache();
90+
});
91+
it('should not cache if no cache is present', () {
92+
backend.expectGET('a', VALUE, times: 2);
93+
94+
var http = new Http(rewriter, backend, futures);
95+
var called = 0;
96+
http.getString('a').then((v) {
97+
expect(v).toBe(VALUE);
98+
called += 1;
99+
});
100+
http.getString('a').then((v) {
101+
expect(v).toBe(VALUE);
102+
called += 10;
103+
});
104+
105+
expect(called).toEqual(0);
106+
107+
backend.flush();
108+
109+
expect(called).toEqual(11);
110+
backend.assertAllGetsCalled();
111+
});
112+
113+
114+
it('should return a pending request', inject(() {
115+
backend.expectGET('a', VALUE, times: 1);
116+
117+
var http = new Http(rewriter, backend, futures);
118+
var called = 0;
119+
http.getString('a', cache: cache).then((v) {
120+
expect(v).toBe(VALUE);
121+
called += 1;
122+
});
123+
http.getString('a', cache: cache).then((v) {
124+
expect(v).toBe(VALUE);
125+
called += 10;
126+
});
127+
128+
expect(called).toEqual(0);
129+
backend.flush();
130+
expect(called).toEqual(11);
131+
backend.assertAllGetsCalled();
132+
}));
133+
134+
135+
it('should not return a pending request after the request is complete', () {
136+
backend.expectGET('a', VALUE, times: 2);
137+
138+
var http = new Http(rewriter, backend, futures);
139+
var called = 0;
140+
http.getString('a', cache: cache).then((v) {
141+
expect(v).toBe(VALUE);
142+
called += 1;
143+
});
144+
145+
expect(called).toEqual(0);
146+
backend.flush();
147+
148+
http.getString('a', cache: cache).then((v) {
149+
expect(v).toBe(VALUE);
150+
called += 10;
151+
});
152+
153+
expect(called).toEqual(1);
154+
backend.flush();
155+
expect(called).toEqual(11);
156+
backend.assertAllGetsCalled();
157+
});
158+
159+
160+
it('should return a cached value if present', () {
161+
var http = new Http(rewriter, backend, futures);
162+
var called = 0;
163+
// The URL string 'f' is primed in the FakeCache
164+
http.getString('f', cache: cache).then((v) {
165+
expect(v).toBe(CACHED_VALUE);
166+
called += 1;
167+
});
168+
169+
expect(called).toEqual(0);
170+
backend.flush();
171+
futures.trigger();
172+
173+
expect(called).toEqual(1);
174+
backend.assertAllGetsCalled();
175+
});
176+
});
177+
}

test/templateurl_spec.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,38 @@ class OnlyCssComponent {
3232
static String $cssUrl = 'simple.css';
3333
}
3434

35+
class PrefixedUrlRewriter extends UrlRewriter {
36+
call(url) => "PREFIX:$url";
37+
}
38+
3539
main() {
40+
describe('loading with http rewriting', () {
41+
var backend;
42+
beforeEach(module((AngularModule module) {
43+
backend = new MockHttpBackend();
44+
module
45+
..directive(HtmlAndCssComponent)
46+
..value(HttpBackend, backend)
47+
..type(UrlRewriter, PrefixedUrlRewriter);
48+
}));
49+
50+
it('should use the UrlRewriter for both HTML and CSS URLs', inject((Http $http, Compiler $compile, Scope $rootScope, Log log, Injector injector) {
51+
52+
backend.expectGET('PREFIX:simple.html', '<div log="SIMPLE">Simple!</div>');
53+
54+
var element = $('<div><html-and-css log>ignore</html-and-css><div>');
55+
$compile(element)(injector, element);
56+
57+
backend.flush();
58+
59+
expect(renderedText(element)).toEqual('@import "PREFIX:simple.css"Simple!');
60+
expect(element[0].nodes[0].shadowRoot.innerHtml).toEqual(
61+
'<style>@import "PREFIX:simple.css"</style><div log="SIMPLE">Simple!</div>'
62+
);
63+
}));
64+
});
65+
66+
3667
describe('async template loading', () {
3768
beforeEach(module((AngularModule module) {
3869
module.factory(Http, (Injector injector) => injector.get(MockHttp));

0 commit comments

Comments
 (0)