diff --git a/modules/angular2/platform/browser.ts b/modules/angular2/platform/browser.ts index 50ee08bfc0..ee4f4498fe 100644 --- a/modules/angular2/platform/browser.ts +++ b/modules/angular2/platform/browser.ts @@ -1,6 +1,7 @@ export {AngularEntrypoint} from 'angular2/src/core/angular_entrypoint'; export { BROWSER_PROVIDERS, + CACHED_TEMPLATE_PROVIDER, ELEMENT_PROBE_PROVIDERS, ELEMENT_PROBE_PROVIDERS_PROD_MODE, inspectNativeElement, diff --git a/modules/angular2/platform/testing/browser.ts b/modules/angular2/platform/testing/browser.ts index 8aeb60f7cd..d344afb44c 100644 --- a/modules/angular2/platform/testing/browser.ts +++ b/modules/angular2/platform/testing/browser.ts @@ -2,12 +2,15 @@ import { TEST_BROWSER_STATIC_PLATFORM_PROVIDERS, ADDITIONAL_TEST_BROWSER_PROVIDERS } from 'angular2/platform/testing/browser_static'; - import {BROWSER_APP_PROVIDERS} from 'angular2/platform/browser'; - - import {CONST_EXPR} from 'angular2/src/facade/lang'; +/** + * Providers for using template cache to avoid actual XHR. + * Re-exported here so that tests import from a single place. + */ +export {CACHED_TEMPLATE_PROVIDER} from 'angular2/platform/browser'; + /** * Default patform providers for testing. */ diff --git a/modules/angular2/src/platform/browser/xhr_cache.dart b/modules/angular2/src/platform/browser/xhr_cache.dart new file mode 100644 index 0000000000..df6619db93 --- /dev/null +++ b/modules/angular2/src/platform/browser/xhr_cache.dart @@ -0,0 +1,48 @@ +library angular2.src.services.xhr_cache; + +import 'dart:async' show Future; +import 'dart:html'; +import 'dart:js' as js; +import 'package:angular2/core.dart'; +import 'package:angular2/src/compiler/xhr.dart'; +import 'package:angular2/src/facade/exceptions.dart' show BaseException; + +/** + * An implementation of XHR that uses a template cache to avoid doing an actual + * XHR. + * + * The template cache needs to be built and loaded into window.$templateCache + * via a separate mechanism. + */ +@Injectable() +class CachedXHR extends XHR { + js.JsObject _cache; + String _baseUri; + + CachedXHR() { + if (js.context.hasProperty(r'$templateCache')) { + this._cache = js.context[r'$templateCache']; + } else { + throw new BaseException( + r'CachedXHR: Template cache was not found in $templateCache.'); + } + this._baseUri = window.location.protocol + + '//' + + window.location.host + + window.location.pathname; + int lastSlash = this._baseUri.lastIndexOf('/'); + this._baseUri = this._baseUri.substring(0, lastSlash + 1); + } + + Future get(String url) { + if (url.startsWith(this._baseUri)) { + url = url.substring(this._baseUri.length); + } + if (this._cache.hasProperty(url)) { + return new Future.value(this._cache[url]); + } else { + return new Future.error( + 'CachedXHR: Did not find cached template for ' + url); + } + } +} diff --git a/modules/angular2/src/platform/browser/xhr_cache.ts b/modules/angular2/src/platform/browser/xhr_cache.ts new file mode 100644 index 0000000000..8a06d4d1b1 --- /dev/null +++ b/modules/angular2/src/platform/browser/xhr_cache.ts @@ -0,0 +1,31 @@ +import {XHR} from 'angular2/src/compiler/xhr'; +import {BaseException} from 'angular2/src/facade/exceptions'; +import {global} from 'angular2/src/facade/lang'; +import {PromiseWrapper} from 'angular2/src/facade/promise'; + +/** + * An implementation of XHR that uses a template cache to avoid doing an actual + * XHR. + * + * The template cache needs to be built and loaded into window.$templateCache + * via a separate mechanism. + */ +export class CachedXHR extends XHR { + private _cache: {[url: string]: string}; + + constructor() { + super(); + this._cache = (global).$templateCache; + if (this._cache == null) { + throw new BaseException('CachedXHR: Template cache was not found in $templateCache.'); + } + } + + get(url: string): Promise { + if (this._cache.hasOwnProperty(url)) { + return PromiseWrapper.resolve(this._cache[url]); + } else { + return PromiseWrapper.reject('CachedXHR: Did not find cached template for ' + url, null); + } + } +} diff --git a/modules/angular2/src/platform/browser_common.ts b/modules/angular2/src/platform/browser_common.ts index 7eae9a1556..2495693f85 100644 --- a/modules/angular2/src/platform/browser_common.ts +++ b/modules/angular2/src/platform/browser_common.ts @@ -1,6 +1,6 @@ import {CONST_EXPR, IS_DART} from 'angular2/src/facade/lang'; import {provide, Provider, Injector, OpaqueToken} from 'angular2/src/core/di'; - +import {XHR} from 'angular2/src/compiler/xhr'; import { PLATFORM_INITIALIZER, PLATFORM_DIRECTIVES, @@ -28,6 +28,7 @@ import {BrowserDetails} from "angular2/src/animate/browser_details"; import {AnimationBuilder} from "angular2/src/animate/animation_builder"; import {BrowserDomAdapter} from './browser/browser_adapter'; import {BrowserGetTestability} from 'angular2/src/platform/browser/testability'; +import {CachedXHR} from 'angular2/src/platform/browser/xhr_cache'; import {wtfInit} from 'angular2/src/core/profile/wtf_init'; import {EventManager, EVENT_MANAGER_PLUGINS} from "angular2/src/platform/dom/events/event_manager"; import { @@ -94,6 +95,9 @@ export const BROWSER_APP_COMMON_PROVIDERS: Array = + CONST_EXPR([new Provider(XHR, {useClass: CachedXHR})]); + export function initDomAdapter() { BrowserDomAdapter.makeCurrent(); wtfInit(); diff --git a/modules/angular2/test/platform/browser/xhr_cache_setter.dart b/modules/angular2/test/platform/browser/xhr_cache_setter.dart new file mode 100644 index 0000000000..45aa836fb8 --- /dev/null +++ b/modules/angular2/test/platform/browser/xhr_cache_setter.dart @@ -0,0 +1,16 @@ +import 'dart:js' as js; + +void setTemplateCache(Map cache) { + if (cache == null) { + if (js.context.hasProperty(r'$templateCache')) { + js.context.deleteProperty(r'$templateCache'); + } + return; + } + + js.JsObject jsMap = new js.JsObject(js.context['Object']); + for (String key in cache.keys) { + jsMap[key] = cache[key]; + } + js.context[r'$templateCache'] = jsMap; +} diff --git a/modules/angular2/test/platform/browser/xhr_cache_setter.ts b/modules/angular2/test/platform/browser/xhr_cache_setter.ts new file mode 100644 index 0000000000..e976333bd6 --- /dev/null +++ b/modules/angular2/test/platform/browser/xhr_cache_setter.ts @@ -0,0 +1,3 @@ +export function setTemplateCache(cache): void { + (window).$templateCache = cache; +} diff --git a/modules/angular2/test/platform/browser/xhr_cache_spec.ts b/modules/angular2/test/platform/browser/xhr_cache_spec.ts new file mode 100644 index 0000000000..63f080bedf --- /dev/null +++ b/modules/angular2/test/platform/browser/xhr_cache_spec.ts @@ -0,0 +1,83 @@ +import {Component, provide} from 'angular2/core'; +import {UrlResolver, XHR} from 'angular2/compiler'; +import { + AsyncTestCompleter, + beforeEach, + beforeEachProviders, + ComponentFixture, + ddescribe, + describe, + expect, + fakeAsync, + iit, + inject, + it, + TestComponentBuilder, + tick, + xit +} from 'angular2/testing_internal'; +import {BaseException} from 'angular2/src/facade/exceptions'; +import {CachedXHR} from 'angular2/src/platform/browser/xhr_cache'; +import {setTemplateCache} from './xhr_cache_setter'; + +export function main() { + describe('CachedXHR', () => { + var xhr: CachedXHR; + + function createCachedXHR(): CachedXHR { + setTemplateCache({'test.html': '
Hello
'}); + return new CachedXHR(); + } + beforeEachProviders(() => [ + provide(UrlResolver, {useClass: TestUrlResolver}), + provide(XHR, {useFactory: createCachedXHR}) + ]); + + it('should throw exception if $templateCache is not found', () => { + setTemplateCache(null); + expect(() => { xhr = new CachedXHR(); }) + .toThrowErrorWith('CachedXHR: Template cache was not found in $templateCache.'); + }); + + it('should resolve the Promise with the cached file content on success', + inject([AsyncTestCompleter], (async) => { + setTemplateCache({'test.html': '
Hello
'}); + xhr = new CachedXHR(); + xhr.get('test.html') + .then((text) => { + expect(text).toEqual('
Hello
'); + async.done(); + }); + })); + + it('should reject the Promise on failure', inject([AsyncTestCompleter], (async) => { + xhr = new CachedXHR(); + xhr.get('unknown.html') + .then((text) => { throw new BaseException('Not expected to succeed.'); }) + .catch((error) => { async.done(); }); + })); + + it('should allow fakeAsync Tests to load components with templateUrl synchronously', + inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { + let fixture: ComponentFixture; + tcb.createAsync(TestComponent).then((f) => { fixture = f; }); + + // This should initialize the fixture. + tick(); + + expect(fixture.debugElement.children[0].nativeElement).toHaveText('Hello'); + }))); + }); +} + +@Component({selector: 'test-cmp', templateUrl: 'test.html'}) +class TestComponent { +} + +class TestUrlResolver extends UrlResolver { + resolve(baseUrl: string, url: string): string { + // Don't use baseUrl to get the same URL as templateUrl. + // This is to remove any difference between Dart and TS tests. + return url; + } +} diff --git a/modules/angular2/test/public_api_spec.ts b/modules/angular2/test/public_api_spec.ts index 8b14a3b587..ec0b1e29e8 100644 --- a/modules/angular2/test/public_api_spec.ts +++ b/modules/angular2/test/public_api_spec.ts @@ -288,6 +288,7 @@ var NG_PLATFORM_BROWSER = [ 'BROWSER_PROVIDERS', 'BrowserDomAdapter', 'By', + 'CACHED_TEMPLATE_PROVIDER', 'DOCUMENT', 'ELEMENT_PROBE_PROVIDERS', 'ELEMENT_PROBE_PROVIDERS_PROD_MODE',