From 01cef016ee81816bc3229863396924ae0dca3bdf Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 23 Jan 2018 14:57:10 +0000 Subject: [PATCH] build(aio): add tests for Firebase redirection (#21712) PR Close #21712 --- aio/package.json | 4 + aio/scripts/deploy-to-firebase.sh | 3 + .../firebase-test-utils/FirebaseGlob.spec.ts | 54 ++++++ aio/tools/firebase-test-utils/FirebaseGlob.ts | 38 ++++ .../FirebaseRedirect.spec.ts | 22 +++ .../firebase-test-utils/FirebaseRedirect.ts | 15 ++ .../FirebaseRedirector.spec.ts | 42 +++++ .../firebase-test-utils/FirebaseRedirector.ts | 36 ++++ .../firebase-test-utils/URLS_TO_REDIRECT.txt | 176 ++++++++++++++++++ .../testFirebaseRedirection.spec.ts | 50 +++++ aio/yarn.lock | 29 ++- 11 files changed, 464 insertions(+), 5 deletions(-) create mode 100644 aio/tools/firebase-test-utils/FirebaseGlob.spec.ts create mode 100644 aio/tools/firebase-test-utils/FirebaseGlob.ts create mode 100644 aio/tools/firebase-test-utils/FirebaseRedirect.spec.ts create mode 100644 aio/tools/firebase-test-utils/FirebaseRedirect.ts create mode 100644 aio/tools/firebase-test-utils/FirebaseRedirector.spec.ts create mode 100644 aio/tools/firebase-test-utils/FirebaseRedirector.ts create mode 100644 aio/tools/firebase-test-utils/URLS_TO_REDIRECT.txt create mode 100644 aio/tools/firebase-test-utils/testFirebaseRedirection.spec.ts diff --git a/aio/package.json b/aio/package.json index 1f4514cd2c..87ae31005a 100644 --- a/aio/package.json +++ b/aio/package.json @@ -43,6 +43,7 @@ "docs-watch": "node tools/transforms/authors-package/watchr.js", "docs-lint": "eslint --ignore-path=\"tools/transforms/.eslintignore\" tools/transforms", "docs-test": "node tools/transforms/test.js", + "redirects-test": "jasmine-ts tools/firebase-test-utils/*.spec.ts", "tools-test": "./scripts/deploy-to-firebase.test.sh && yarn docs-test && yarn boilerplate:test && jasmine tools/ng-packages-installer/index.spec.js", "serve-and-sync": "concurrently --kill-others \"yarn docs-watch\" \"yarn start\"", "boilerplate:add": "node ./tools/examples/example-boilerplate add", @@ -96,6 +97,7 @@ "archiver": "^1.3.0", "canonical-path": "^0.0.2", "chalk": "^2.1.0", + "cjson": "^0.5.0", "codelyzer": "~2.0.0", "concurrently": "^3.4.0", "cross-spawn": "^5.1.0", @@ -116,6 +118,7 @@ "image-size": "^0.5.1", "jasmine-core": "^2.8.0", "jasmine-spec-reporter": "^4.1.0", + "jasmine-ts": "^0.2.1", "jsdom": "^9.12.0", "karma": "^1.7.0", "karma-chrome-launcher": "^2.1.1", @@ -145,6 +148,7 @@ "unist-util-visit-parents": "^1.1.1", "vrsource-tslint-rules": "^4.0.1", "watchr": "^3.0.1", + "xregexp": "^4.0.0", "yargs": "^7.0.2" } } diff --git a/aio/scripts/deploy-to-firebase.sh b/aio/scripts/deploy-to-firebase.sh index 3c28912e68..27293cd07e 100755 --- a/aio/scripts/deploy-to-firebase.sh +++ b/aio/scripts/deploy-to-firebase.sh @@ -92,6 +92,9 @@ fi # Include any mode-specific files cp -rf src/extra-files/$deployEnv/. dist/ + # Check that the redirects are setup correctly + yarn redirects-test + # Check payload size yarn payload-size diff --git a/aio/tools/firebase-test-utils/FirebaseGlob.spec.ts b/aio/tools/firebase-test-utils/FirebaseGlob.spec.ts new file mode 100644 index 0000000000..2956091f74 --- /dev/null +++ b/aio/tools/firebase-test-utils/FirebaseGlob.spec.ts @@ -0,0 +1,54 @@ +import { FirebaseGlob } from './FirebaseGlob'; + +describe('FirebaseGlob', () => { + + describe('test', () => { + it('should match * parts', () => { + testGlob('asdf/*.jpg', + ['asdf/asdf.jpg', 'asdf/asdf_asdf.jpg', 'asdf/.jpg'], + ['asdf/asdf/asdf.jpg', 'xxxasdf/asdf.jpgxxx']); + }); + + it('should match ** parts', () => { + testGlob('asdf/**.jpg', + ['asdf/asdf.jpg', 'asdf/asdf_asdf.jpg', 'asdf/asdf/asdf.jpg', 'asdf/asdf/asdf/asdf/asdf.jpg'], + ['/asdf/asdf.jpg', 'asdff/asdf.jpg', 'xxxasdf/asdf.jpgxxx']); + + testGlob('**/*.js', + ['asdf/asdf.js', 'asdf/asdf/asdfasdf_asdf.js', '/asdf/asdf.js', '/asdf/aasdf-asdf.2.1.4.js'], + ['/asdf/asdf.jpg', 'asdf.js']); + }); + + it('should match groups', () => { + testGlob('asdf/*.(jpg|jpeg)', + ['asdf/asdf.jpg', 'asdf/asdf_asdf.jpeg'], + ['/asdf/asdf.jpg', 'asdff/asdf.jpg']); + }); + + it('should match named parts', () => { + testGlob('/api/:package/:api-*', + ['/api/common/NgClass-directive', '/api/core/Renderer-class'], + ['/moo/common/NgClass-directive', '/api/common/', '/api/common/NgClass']); + }); + + it('should match wildcard named parts', () => { + testGlob('/api/:rest*', + ['/api/a', '/api/a/b'], + ['/apx/a', '/apx/a/b']); + }); + }); + + describe('match', () => { + it('should extract the named parts', () => { + const glob = new FirebaseGlob('/api/:package/:api-*'); + const match: any = glob.match('/api/common/NgClass-directive'); + expect(match).toEqual({package: 'common', api: 'NgClass'}); + }); + }); +}); + +function testGlob(pattern: string, matches: string[], nonMatches: string[]) { + const glob = new FirebaseGlob(pattern); + matches.forEach(url => expect(glob.test(url)).toBe(true, url)); + nonMatches.forEach(url => expect(glob.test(url)).toBe(false, url)); +} diff --git a/aio/tools/firebase-test-utils/FirebaseGlob.ts b/aio/tools/firebase-test-utils/FirebaseGlob.ts new file mode 100644 index 0000000000..413697dfd9 --- /dev/null +++ b/aio/tools/firebase-test-utils/FirebaseGlob.ts @@ -0,0 +1,38 @@ +import * as XRegExp from 'xregexp'; + +const questionExpr = /\?([^(])/g; +const catchAllNamedMatchExpr = /\/:([A-Za-z]+)\*/g +const namedMatchExpr = /\/:([A-Za-z]+)([^/]*)/g +const wildcardSegmentExpr = /(^\/).🐷\//; + +export class FirebaseGlob { + pattern: string; + regex: XRegExp; + constructor(glob: string) { + const pattern = glob + .replace('.', '\\.') + .replace(questionExpr, '[^/]$1') + .replace(catchAllNamedMatchExpr, '/(?<$1>.+)') + .replace(namedMatchExpr, '/(?<$1>[^/]+)$2') + .replace('**', '.🐷') + .replace('*', '[^/]*') + .replace(wildcardSegmentExpr, '(?:$1|/.*/)') + .replace('.🐷', '.*'); + this.pattern = `^${pattern}$`; + this.regex = XRegExp(this.pattern); + } + + test(url: string) { + return XRegExp.test(url, this.regex); + } + + match(url: string) { + const match = XRegExp.exec(url, this.regex); + if (match) { + const result = {}; + const names = (this.regex as any).xregexp.captureNames || []; + names.forEach(name => result[name] = match[name]); + return result; + } + } +} diff --git a/aio/tools/firebase-test-utils/FirebaseRedirect.spec.ts b/aio/tools/firebase-test-utils/FirebaseRedirect.spec.ts new file mode 100644 index 0000000000..1145ca6f8a --- /dev/null +++ b/aio/tools/firebase-test-utils/FirebaseRedirect.spec.ts @@ -0,0 +1,22 @@ +import { FirebaseRedirect } from './FirebaseRedirect'; + +describe('FirebaseRedirect', () => { + describe('replace', () => { + it('should inject the named captures into the destination', () => { + const redirect = new FirebaseRedirect('/api/:package/:api-*', '<:package><:api>'); + const newUrl = redirect.replace('/api/common/NgClass-directive'); + expect(newUrl).toEqual(''); + }); + + it('should handle empty ** sections', () => { + const redirect2 = new FirebaseRedirect('/**/a/b', '/xxx'); + expect(redirect2.replace('/a/b')).toEqual('/xxx'); + }); + + it('should return `undefined` if the redirect failed to match', () => { + const redirect = new FirebaseRedirect('/api/:package/:api-*', '<:package><:api>'); + const newUrl = redirect.replace('/common/NgClass-directive'); + expect(newUrl).toBe(undefined); + }); + }); +}); \ No newline at end of file diff --git a/aio/tools/firebase-test-utils/FirebaseRedirect.ts b/aio/tools/firebase-test-utils/FirebaseRedirect.ts new file mode 100644 index 0000000000..9a41f17040 --- /dev/null +++ b/aio/tools/firebase-test-utils/FirebaseRedirect.ts @@ -0,0 +1,15 @@ +import * as XRegExp from 'xregexp'; +import { FirebaseGlob } from './FirebaseGlob'; + +export class FirebaseRedirect { + glob = new FirebaseGlob(this.source); + constructor(public source: string, public destination: string) {} + + replace(url: string) { + const match = this.glob.match(url); + if (match) { + const replacers = Object.keys(match).map(name => [ XRegExp(`:${name}`, 'g'), match[name] ]); + return XRegExp.replaceEach(this.destination, replacers); + } + } +} diff --git a/aio/tools/firebase-test-utils/FirebaseRedirector.spec.ts b/aio/tools/firebase-test-utils/FirebaseRedirector.spec.ts new file mode 100644 index 0000000000..9d7c27401a --- /dev/null +++ b/aio/tools/firebase-test-utils/FirebaseRedirector.spec.ts @@ -0,0 +1,42 @@ +import { FirebaseRedirector } from './FirebaseRedirector'; + +describe('FirebaseRedirector', () => { + it('should replace with the first matching redirect', () => { + const redirector = new FirebaseRedirector([ + { source: '/a/b/c', destination: '/X/Y/Z' }, + { source: '/a/:foo/c', destination: '/X/:foo/Z' }, + { source: '/**/:foo/c', destination: '/A/:foo/zzz' }, + ]); + expect(redirector.redirect('/a/b/c')).toEqual('/X/Y/Z'); + expect(redirector.redirect('/a/moo/c')).toEqual('/X/moo/Z'); + expect(redirector.redirect('/x/y/a/b/c')).toEqual('/A/b/zzz'); + expect(redirector.redirect('/x/y/c')).toEqual('/A/y/zzz'); + }); + + it('should return the original url if no redirect matches', () => { + const redirector = new FirebaseRedirector([ + { source: 'x', destination: 'X' }, + { source: 'y', destination: 'Y' }, + { source: 'z', destination: 'Z' }, + ]); + expect(redirector.redirect('a')).toEqual('a'); + }); + + it('should recursively redirect', () => { + const redirector = new FirebaseRedirector([ + { source: 'a', destination: 'b' }, + { source: 'b', destination: 'c' }, + { source: 'c', destination: 'd' }, + ]); + expect(redirector.redirect('a')).toEqual('d'); + }); + + it('should throw if stuck in an infinite loop', () => { + const redirector = new FirebaseRedirector([ + { source: 'a', destination: 'b' }, + { source: 'b', destination: 'c' }, + { source: 'c', destination: 'a' }, + ]); + expect(() => redirector.redirect('a')).toThrowError('infinite redirect loop'); + }); +}); \ No newline at end of file diff --git a/aio/tools/firebase-test-utils/FirebaseRedirector.ts b/aio/tools/firebase-test-utils/FirebaseRedirector.ts new file mode 100644 index 0000000000..fc1c611e80 --- /dev/null +++ b/aio/tools/firebase-test-utils/FirebaseRedirector.ts @@ -0,0 +1,36 @@ +import { FirebaseRedirect } from './FirebaseRedirect'; + +export interface FirebaseRedirectConfig { + source: string; + destination: string; +} + +export class FirebaseRedirector { + private redirects: FirebaseRedirect[]; + constructor(redirects: FirebaseRedirectConfig[]) { + this.redirects = redirects.map(redirect => new FirebaseRedirect(redirect.source, redirect.destination)); + } + + redirect(url: string) { + let ttl = 50; + while(ttl > 0) { + const newUrl = this.doRedirect(url); + if (newUrl === url) { + return url; + } else { + url = newUrl; + ttl--; + } + } + throw new Error('infinite redirect loop') + } + private doRedirect(url: string) { + for(let i = 0; i < this.redirects.length; i++) { + const newUrl = this.redirects[i].replace(url); + if (newUrl !== undefined) { + return newUrl; + } + } + return url; + } +} diff --git a/aio/tools/firebase-test-utils/URLS_TO_REDIRECT.txt b/aio/tools/firebase-test-utils/URLS_TO_REDIRECT.txt new file mode 100644 index 0000000000..b7b46c88ee --- /dev/null +++ b/aio/tools/firebase-test-utils/URLS_TO_REDIRECT.txt @@ -0,0 +1,176 @@ +/api/api/core/ElementRef api/core/ElementRef +/api/common/Control-class /api/forms/FormControl +/api/common/CORE_DIRECTIVES /api/common/CommonModule +/api/common/DatePipe-class /api/common/DatePipe +/api/common/NgClass-directive.html /api/common/NgClass +/api/common/NgFor-directive.html /api/common/NgForOf +/api/common/NgModel-directive /api/forms/NgModel +/api/common/SelectControlValueAccessor-directive /api/forms/SelectControlValueAccessor +/api/common/SlicePipe-class.html /api/common/SlicePipe +/api/core/AnimationStateDeclarationMetadata /api/animation +/api/core/DoCheck-interface.html /api/core/DoCheck +/api/core/HostBinding-var /api/core/HostBinding +/api/core/OpaqueToken-class /api/core/OpaqueToken +/api/core/OptionalMetadata-class /api/core/OptionalMetadata +/api/core/PLATFORM_PIPES /api/common +/api/core/Provider-class.html /api/core/Provider +/api/core/Renderer-class /api/core/Renderer +/api/core/testing/async-function /api/core/testing/async +/api/core/testing/index/async-function /api/core/testing/async +/api/core/testing/index/TestBed-class.html /api/core/testing/TestBed +/api/core/testing/inject-function /api/core/testing/inject +/api/core/testing/inject-function.html /api/core/testing/inject +/api/http/Headers-class /api/http/Headers +/api/http/Headers-class.html /api/http/Headers +/api/http/HTTP_PROVIDERS-let /api/http/HttpModule +/api/http/testing/index/MockBackend-class /api/http/testing/MockBackend +/api/http/testing/index/MockBackend-class.html /api/http/testing/MockBackend +/api/platform-browser-dynamic/testing/index/platformBrowserDynamicTesting-let.html /api/platform-browser-dynamic/testing/platformBrowserDynamicTesting +/api/platform-browser/AnimationDriver /api/animations/browser/AnimationDriver +/api/router/Route-class /api/router/Route +/api/router/RouterLink-directive /api/router/RouterLink +/api/testing/tick-function /api/core/testing/tick +/api/upgrade/static/downgradeComponent-function /api/upgrade/static/downgradeComponent +/api/upgrade/static/downgradeComponent-function.html /api/upgrade/static/downgradeComponent +/api/upgrade/static/index/downgradeComponent-function /api/upgrade/static/downgradeComponent +/api/upgrade/static/UpgradeModule-class /api/upgrade/static/UpgradeModule +/docs/js/latest/api/ /api/* +/docs/js/latest/api/animate/AnimationBuilder-class /api/animate/AnimationBuilder +/docs/js/latest/api/animate/CssAnimationBuilder-class.html /api/animate/CssAnimationBuilder +/docs/js/latest/api/animations/index/trigger-function /api/animations/trigger +/docs/js/latest/api/common/ControlGroup-class.html /api/forms/FormGroup +/docs/js/latest/api/common/index/DatePipe-pipe /api/common/DatePipe +/docs/js/latest/api/common/index/Location-class.html /api/common/Location +/docs/js/latest/api/common/index/MaxLengthValidator-directive.html /api/forms/MaxLengthValidator +/docs/js/latest/api/common/index/NgFor-directive /api/common/NgForOf +/docs/js/latest/api/common/index/NgFor-directive.html /api/common/NgForOf +/docs/js/latest/api/common/index/NgFor-type-alias /api/common/NgForOf +/docs/js/latest/api/common/index/NgFor-type-alias.html /api/common/NgForOf +/docs/js/latest/api/common/index/NgTemplateOutlet-directive /api/common/NgTemplateOutlet +/docs/js/latest/api/common/NgStyle-directive /api/common/NgStyle +/docs/js/latest/api/core/DynamicComponentLoader-class.html /api/core/DynamicComponentLoader +/docs/js/latest/api/core/HostListener-var /api/core/HostListener +/docs/js/latest/api/core/index/AfterViewChecked-class /api/core/AfterViewChecked +/docs/js/latest/api/core/index/AnimationStateTransitionMetadata-class.html /api/core/AnimationStateTransitionMetadata +/docs/js/latest/api/core/index/AnimationStateTransitionMetadata-type-alias /api/core/AnimationStateTransitionMetadata +/docs/js/latest/api/core/index/ApplicationModule-class /api/core/ApplicationModule +/docs/js/latest/api/core/index/ApplicationRef-class /api/core/ApplicationRef +/docs/js/latest/api/core/index/ChangeDetectorRef-class /api/core/ChangeDetectorRef +/docs/js/latest/api/core/index/ComponentFactory-class /api/core/ComponentFactory +/docs/js/latest/api/core/index/DebugNode-class /api/core/DebugNode +/docs/js/latest/api/core/index/destroyPlatform-function /api/core/destroyPlatform +/docs/js/latest/api/core/index/DirectiveMetadata-class /api/core/Directive +/docs/js/latest/api/core/index/ErrorHandler-class /api/core/ErrorHandler +/docs/js/latest/api/core/index/EventEmitter-class /api/core/EventEmitter +/docs/js/latest/api/core/index/EventEmitter-class.html /api/core/EventEmitter +/docs/js/latest/api/core/index/getPlatform-function /api/core/getPlatform +/docs/js/latest/api/core/index/NgModule-interface /api/core/NgModule +/docs/js/latest/api/core/index/NgModuleRef-class /api/core/NgModuleRef +/docs/js/latest/api/core/index/NgModuleRef-class.html /api/core/NgModuleRef +/docs/js/latest/api/core/index/OnInit-class /api/core/OnInit +/docs/js/latest/api/core/index/OnInit-class.html /api/core/OnInit +/docs/js/latest/api/core/index/OnInit-interface /api/core/OnInit +/docs/js/latest/api/core/index/PlatformRef-class /api/core/PlatformRef +/docs/js/latest/api/core/index/QueryList-class.html /api/core/QueryList +/docs/js/latest/api/core/index/RenderComponentType-class /api/core/RenderComponentType +/docs/js/latest/api/core/index/Renderer2-class.html /api/core/Renderer2 +/docs/js/latest/api/core/index/state-function.html /api/core/state +/docs/js/latest/api/core/index/transition-function.html /api/core/transition +/docs/js/latest/api/core/index/trigger-function /api/core/trigger +/docs/js/latest/api/core/index/trigger-function.html /api/core/trigger +/docs/js/latest/api/core/index/ViewChild-decorator /api/core/ViewChild +/docs/js/latest/api/core/index/wtfLeave-let /api/core/wtfLeave +/docs/js/latest/api/core/Injector-class.html /api/core/Injector +/docs/js/latest/api/core/Query-var /api/core/Query +/docs/js/latest/api/core/ResolvedProvider-interface.html /api/core/ResolvedProvider +/docs/js/latest/api/core/testing/index/TestBed-class /api/core/testing/TestBed +/docs/js/latest/api/core/testing/index/tick-function /api/core/testing/tick +/docs/js/latest/api/core/testing/index/tick-function.html /api/core/testing/tick +/docs/js/latest/api/core/ViewContainerRef-class.html /api/core/ViewContainerRef +/docs/js/latest/api/forms/index/AbstractControl-class /api/forms/AbstractControl +/docs/js/latest/api/forms/index/AbstractControl-class.html /api/forms/AbstractControl +/docs/js/latest/api/forms/index/FormArray-class.html /api/forms/FormArray +/docs/js/latest/api/forms/index/FormBuilder-class.html /api/forms/FormBuilder +/docs/js/latest/api/forms/index/NG_VALIDATORS-let /api/forms/NG_VALIDATORS +/docs/js/latest/api/forms/index/Validator-interface.html /api/forms/Validator +/docs/js/latest/api/http/ConnectionBackend-class /api/http/ConnectionBackend +/docs/js/latest/api/http/index/Http-class.html /api/http/Http +/docs/js/latest/api/http/index/Jsonp-class.html /api/http/Jsonp +/docs/js/latest/api/http/index/ResponseOptions-class.html /api/http/ResponseOptions +/docs/js/latest/api/http/index/URLSearchParams-class /api/http/URLSearchParams +/docs/js/latest/api/http/index/XHRConnection-class /api/http/XHRConnection +/docs/js/latest/api/http/index/XHRConnection-class.html /api/http/XHRConnection +/docs/js/latest/api/http/testing/index/MockConnection-class.html /api/http/testing/MockConnection +/docs/js/latest/api/http/testing/MockBackend-class /api/http/testing/MockBackend +/docs/js/latest/api/platform-browser-dynamic/index/platformBrowserDynamic-let.html /api/platform-browser-dynamic/platformBrowserDynamic +/docs/js/latest/api/platform-browser-dynamic/testing/index/BrowserDynamicTestingModule-class.html /api/platform-browser-dynamic/testing/BrowserDynamicTestingModule +/docs/js/latest/api/platform-browser/animations/index/BrowserAnimationsModule-class /api/platform-browser/animations/BrowserAnimationsModule-class +/docs/js/latest/api/platform-browser/animations/index/BrowserAnimationsModule-class.html /api/platform-browser/animations/BrowserAnimationsModule-class.html +/docs/js/latest/api/platform-browser/animations/index/NoopAnimationsModule-class.html /api/platform-browser/animations/NoopAnimationsModule-class.html +/docs/js/latest/api/platform-browser/index/DomSanitizer-class /api/platform-browser/DomSanitizer +/docs/js/latest/api/platform-browser/index/Meta-class /api/platform-browser/Meta +/docs/js/latest/api/platform-browser/index/MetaDefinition-type-alias /api/platform-browser/MetaDefinition +/docs/js/latest/api/platform-browser/index/SafeUrl-interface /api/platform-browser/SafeUrl +/docs/js/latest/api/platform-browser/index/Title-class /api/platform-browser/Title +/docs/js/latest/api/platform-browser/index/Title-class.html /api/platform-browser/Title +/docs/js/latest/api/platform-server/index/PlatformState-class /api/platform-server/PlatformState +/docs/js/latest/api/platform-server/index/PlatformState-class.html /api/platform-server/PlatformState +/docs/js/latest/api/platform-webworker /api/platform-webworker +/docs/js/latest/api/platform-webworker/index/MessageBus-class /api/platform-webworker/MessageBus +/docs/js/latest/api/router/index/ActivatedRoute-interface /api/router/ActivatedRoute +/docs/js/latest/api/router/index/CanActivate-interface.html /api/router/CanActivate +/docs/js/latest/api/router/index/CanActivateChild-interface /api/router/CanActivateChild +/docs/js/latest/api/router/index/CanDeactivate-interface.html /api/router/CanDeactivate +/docs/js/latest/api/router/index/CanLoad-interface /api/router/CanLoad +/docs/js/latest/api/router/index/CanLoad-interface.html /api/router/CanLoad +/docs/js/latest/api/router/index/NavigationEnd-class /api/router/NavigationEnd +/docs/js/latest/api/router/index/provideRoutes-function /api/router/provideRoutes +/docs/js/latest/api/router/index/provideRoutes-function.html /api/router/provideRoutes +/docs/js/latest/api/router/index/Router-class /api/router/Router +/docs/js/latest/api/router/index/RouterLink-directive /api/router/RouterLink +/docs/js/latest/api/router/index/Routes-type-alias /api/router/Routes +/docs/js/latest/api/router/index/UrlSegment-class /api/router/UrlSegment +/docs/js/latest/api/router/index/UrlSerializer-class.html /api/router/UrlSerializer +/docs/js/latest/api/router/Instruction-class /api/router/Instruction +/docs/js/latest/api/router/Location-class /api/router/Location +/docs/js/latest/api/router/OnActivate-interface /api/router/OnActivate +/docs/js/latest/api/router/Redirect-class.html /api/router/Redirect +/docs/js/latest/api/router/RouteDefinition-interface.html /api/router/RouteDefinition +/docs/js/latest/api/router/RouteParams-class /api/router/RouteParams +/docs/js/latest/api/router/RouteParams-class.html /api/router/RouteParams +/docs/js/latest/api/router/Router-class.html /api/router/Router +/docs/js/latest/api/router/testing /api/router/testing +/docs/js/latest/api/upgrade/index/UpgradeAdapter-class.html /api/upgrade/UpgradeAdapter +/docs/js/latest/api/upgrade/static/UpgradeModule-class /api/upgrade/static/UpgradeModule +/docs/js/latest/api/upgrade/static/UpgradeModule-class.html /api/upgrade/static/UpgradeModule +/docs/js/latest/cookbook/ts-to-js.html https://github.com/angular/angular/blob/master/aio/content/guide/change-log.md#es6--described-in-typescript-to-javascript-2016-11-14 +/docs/js/latest/glossary /guide/glossary +/docs/js/latest/guide/ /guide +/docs/js/latest/guide/lifecycle-hooks /guide/lifecycle-hooks +/docs/js/latest/guide/ngmodule /guide/ngmodule* +/docs/js/latest/resources /resources* +/docs/latest/tutorial /tutorial* +/docs/styleguide /guide/styleguide +/docs/styleguide.html /guide/styleguide +/docs/ts/latest/api/core/HostBinding-var.html /api/core/HostBinding +/docs/ts/latest/api/core/index/BaseException-class.html /api/core/BaseException +/docs/ts/latest/api/core/index/PLATFORM_PIPES-let.html /api/common +/docs/ts/latest/api/core/OnInit-interface.html /api/core/OnInit +/docs/ts/latest/api/core/OpaqueToken-class.html /api/core/OpaqueToken +/docs/ts/latest/api/core/OptionalMetadata-class.html /api/core/OptionalMetadata +/docs/ts/latest/api/core/testing/index/async-function.html /api/core/testing/async +/docs/ts/latest/api/core/testing/index/fakeAsync-function.html /api/core/testing/fakeAsync +/docs/ts/latest/api/core/testing/index/TestComponentRenderer-class.html /api/core/testing/TestComponentRenderer +/docs/ts/latest/api/core/testing/index/tick-function.html /api/core/testing/tick +/docs/ts/latest/api/http/Connection-class.html /api/http/Connection +/docs/ts/latest/api/http/testing/index/MockBackend-class.html /api/http/testing/MockBackend +/docs/ts/latest/api/http/testing/index/MockConnection-class.html /api/http/testing/MockConnection +/docs/ts/latest/api/platform-browser-dynamic/index/workerAppDynamicPlatform-let.html /api/platform-browser-dynamic/workerAppDynamicPlatform +/docs/ts/latest/api/testing/fakeAsync-function.html /api/core/testing/fakeAsync +/docs/ts/latest/cookbook/ts-to-js.html https://github.com/angular/angular/blob/master/aio/content/guide/change-log.md#es6--described-in-typescript-to-javascript-2016-11-14 +/guide/learning-angular /guide/quickstart +/guide/learning-angular.html /guide/quickstart +/news https://blog.angular.io/ +/news.html https://blog.angular.io/ +/testing /guide/testing +/testing/first-app-tests.html /guide/testing \ No newline at end of file diff --git a/aio/tools/firebase-test-utils/testFirebaseRedirection.spec.ts b/aio/tools/firebase-test-utils/testFirebaseRedirection.spec.ts new file mode 100644 index 0000000000..e02f3a35c0 --- /dev/null +++ b/aio/tools/firebase-test-utils/testFirebaseRedirection.spec.ts @@ -0,0 +1,50 @@ +const { readFileSync } = require('fs'); +const path = require('canonical-path'); +const cjson = require('cjson'); + +import { FirebaseRedirector, FirebaseRedirectConfig } from './FirebaseRedirector'; + + +describe('sitemap urls', () => { + loadSitemapUrls().forEach(url => { + it('should not redirect any urls in the sitemap', () => { + expect(getRedirector().redirect(url)).toEqual(url); + }); + }); +}); + +describe('redirected urls', () => { + loadLegacyUrls().forEach(urlPair => { + it('should redirect the legacy urls', () => { + const redirector = getRedirector(); + expect(redirector.redirect(urlPair[0])).not.toEqual(urlPair[0]); + if (urlPair[1]) { + expect(redirector.redirect(urlPair[0])).toEqual(urlPair[1]); + } + }); + }); +}); + +function getRedirector() { + return new FirebaseRedirector(loadRedirects()); +} + +function loadRedirects(): FirebaseRedirectConfig[] { + const pathToFirebaseJSON = path.resolve(__dirname, '../../firebase.json'); + const contents = cjson.load(pathToFirebaseJSON); + return contents.hosting.redirects; +} + +function loadSitemapUrls() { + const pathToSiteMap = path.resolve(__dirname, '../../src/generated/sitemap.xml'); + const xml = readFileSync(pathToSiteMap, 'utf8'); + const urls: string[] = []; + xml.replace(/([^<]+)<\/loc>/g, (_, loc) => urls.push(loc.replace('%%DEPLOYMENT_HOST%%', ''))); + return urls; +} + +function loadLegacyUrls() { + const pathToLegacyUrls = path.resolve(__dirname, 'URLS_TO_REDIRECT.txt'); + const urls = readFileSync(pathToLegacyUrls, 'utf8').split('\n').map(line => line.split('\t')); + return urls; +} diff --git a/aio/yarn.lock b/aio/yarn.lock index b45153cbd0..7c02bc1b76 100644 --- a/aio/yarn.lock +++ b/aio/yarn.lock @@ -1426,6 +1426,12 @@ cjson@^0.3.1: dependencies: json-parse-helpfulerror "^1.0.3" +cjson@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cjson/-/cjson-0.5.0.tgz#a0f48601e016164dfb2c6d891e380c96cada9839" + dependencies: + json-parse-helpfulerror "^1.0.3" + clap@^1.0.9: version "1.2.3" resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51" @@ -4634,6 +4640,15 @@ jasmine-spec-reporter@^4.1.0: dependencies: colors "1.1.2" +jasmine-ts@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/jasmine-ts/-/jasmine-ts-0.2.1.tgz#7fe5911d98ea22cc257efe98f9728ab66ac0e90e" + dependencies: + jasmine "^2.6.0" + ts-node "^3.2.0" + typescript "^2.4.1" + yargs "^8.0.2" + jasmine@^2.5.3, jasmine@^2.6.0: version "2.8.0" resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.8.0.tgz#6b089c0a11576b1f16df11b80146d91d4e8b8a3e" @@ -8351,7 +8366,7 @@ tryit@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" -ts-node@^3.0.2, ts-node@^3.3.0: +ts-node@^3.0.2, ts-node@^3.2.0, ts-node@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-3.3.0.tgz#c13c6a3024e30be1180dd53038fc209289d4bf69" dependencies: @@ -8445,14 +8460,14 @@ typescript@2.4: version "2.4.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.2.tgz#f8395f85d459276067c988aa41837a8f82870844" +typescript@^2.4.1, typescript@~2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" + typescript@^2.5.3: version "2.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.3.tgz#df3dcdc38f3beb800d4bc322646b04a3f6ca7f0d" -typescript@~2.6.1: - version "2.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" - uglify-es@^3.3.4: version "3.3.5" resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.5.tgz#cf7e695da81999f85196b15e2978862f13212f88" @@ -9294,6 +9309,10 @@ xmlhttprequest-ssl@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d" +xregexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020" + xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"