diff --git a/aio/src/app/documents/document.service.spec.ts b/aio/src/app/documents/document.service.spec.ts index 4acddefe7c..8b083e391b 100644 --- a/aio/src/app/documents/document.service.spec.ts +++ b/aio/src/app/documents/document.service.spec.ts @@ -67,29 +67,17 @@ describe('DocumentService', () => { expect(latestDocument).toEqual(doc1); }); - // HACK: PREPARE FOR CHANGING TO CASE-INSENSITIVE URLS - it('should attempt disambiguated document paths if the document is not found on the server', () => { - let currentDocument: DocumentContents|undefined; - const notFoundDoc = { id: FILE_NOT_FOUND_ID, contents: '

Page Not Found

' }; - const { docService, logger } = getServices('missing/Doc-1'); - docService.currentDocument.subscribe(doc => currentDocument = doc); - - // Initial request return 404. - httpMock.expectOne({url: 'generated/docs/missing/Doc-1.json'}).flush(null, {status: 404, statusText: 'NOT FOUND'}); - httpMock.expectOne({url: 'generated/docs/missing/d_oc-1.json'}).flush(null, {status: 404, statusText: 'NOT FOUND'}); - httpMock.expectOne({url: 'generated/docs/missing/d_oc.json'}).flush(null, {status: 404, statusText: 'NOT FOUND'}); - expect(logger.output.error).toEqual([ - [jasmine.any(Error)] - ]); - expect(logger.output.error[0][0].message).toEqual(`Document file not found at 'missing/Doc-1'`); - - // Subsequent request for not-found document. - logger.output.error = []; - httpMock.expectOne(CONTENT_URL_PREFIX + 'file-not-found.json').flush(notFoundDoc); - expect(logger.output.error).toEqual([]); // does not report repeated errors - expect(currentDocument).toEqual(notFoundDoc); + it('should encode the request path to be case-insensitive', () => { + const { docService, locationService } = getServices('initial/Doc'); + docService.currentDocument.subscribe(); + httpMock.expectOne(CONTENT_URL_PREFIX + 'initial/d_oc.json').flush({}); + locationService.go('NEW/Doc'); + httpMock.expectOne(CONTENT_URL_PREFIX + 'n_e_w_/d_oc.json').flush({}); + locationService.go('doc_with_underscores'); + httpMock.expectOne(CONTENT_URL_PREFIX + 'doc__with__underscores.json').flush({}); + locationService.go('DOC_WITH_UNDERSCORES'); + httpMock.expectOne(CONTENT_URL_PREFIX + 'd_o_c___w_i_t_h___u_n_d_e_r_s_c_o_r_e_s_.json').flush({}); }); - // END HACK: PREPARE FOR CHANGING TO CASE-INSENSITIVE URLS it('should emit the not-found document if the document is not found on the server', () => { let currentDocument: DocumentContents|undefined; @@ -119,7 +107,7 @@ describe('DocumentService', () => { docService.currentDocument.subscribe(doc => currentDocument = doc); - httpMock.expectOne({}).flush(null, { status: 404, statusText: 'NOT FOUND'}); + httpMock.expectOne({}).flush(null, {status: 404, statusText: 'NOT FOUND'}); expect(currentDocument).toEqual(hardCodedNotFoundDoc); // now check that we haven't killed the currentDocument observable sequence @@ -143,7 +131,7 @@ describe('DocumentService', () => { [jasmine.any(Error)] ]); expect(logger.output.error[0][0].message) - .toEqual(`Error fetching document 'initial/doc': (Http failure response for generated/docs/initial/doc.json: 500 Server Error)`); + .toEqual(`Error fetching document 'initial/doc': (Http failure response for generated/docs/initial/doc.json: 500 Server Error)`); locationService.go('new/doc'); httpMock.expectOne({}).flush(doc1); diff --git a/aio/src/app/documents/document.service.ts b/aio/src/app/documents/document.service.ts index 9d521f3232..73ab9a0ec3 100644 --- a/aio/src/app/documents/document.service.ts +++ b/aio/src/app/documents/document.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpErrorResponse } from '@angular/common/http'; -import { AsyncSubject, Observable, of, throwError } from 'rxjs'; +import { AsyncSubject, Observable, of } from 'rxjs'; import { catchError, switchMap, tap } from 'rxjs/operators'; import { DocumentContents } from './document-contents'; @@ -53,7 +53,7 @@ export class DocumentService { } private fetchDocument(id: string): Observable { - const requestPath = `${DOC_CONTENT_URL_PREFIX}${id}.json`; + const requestPath = `${DOC_CONTENT_URL_PREFIX}${encodeToLowercase(id)}.json`; const subject = new AsyncSubject(); this.logger.log('fetching document from', requestPath); @@ -66,20 +66,6 @@ export class DocumentService { throw Error('Invalid data'); } }), - // HACK: PREPARE FOR CHANGING TO CASE-INSENSITIVE URLS - catchError((error: HttpErrorResponse) => { - const encodedPath = encodeToLowercase(requestPath); - return error.status === 404 && encodedPath !== requestPath ? - this.http.get(encodedPath) : - throwError(error); - }), - catchError((error: HttpErrorResponse) => { - const disambiguatedPath = convertDisambiguatedPath(requestPath); - return error.status === 404 && disambiguatedPath !== requestPath ? - this.http.get(disambiguatedPath) : - throwError(error); - }), - // END HACK: PREPARE FOR CHANGING TO CASE-INSENSITIVE URLS catchError((error: HttpErrorResponse) => { return error.status === 404 ? this.getFileNotFoundDoc(id) : this.getErrorDoc(id, error); }), @@ -123,18 +109,3 @@ export class DocumentService { function encodeToLowercase(str: string): string { return str.replace(/[A-Z_]/g, char => char.toLowerCase() + '_'); } - -/** - * A temporary function to deal with a future change to URL disambiguation. - * - * Currently there are disambiguated URLs such as `INJECTOR-0` and `Injector-1`, which - * will attempt to load their document contents from `injector-0.json` and `injector-1.json` - * respectively. In a future version of the AIO app, the disambiguation will be changed to - * escape the upper-case characters instead. - * - * This function will be called if the current AIO is trying to request documents from a - * server that has been updated to use the new disambiguated URLs. - */ -function convertDisambiguatedPath(str: string): string { - return encodeToLowercase(str.replace(/-\d+\.json$/, '.json')); -} diff --git a/aio/tests/deployment/e2e/smoke-tests.e2e-spec.ts b/aio/tests/deployment/e2e/smoke-tests.e2e-spec.ts index 14dced3048..2d58484fc6 100644 --- a/aio/tests/deployment/e2e/smoke-tests.e2e-spec.ts +++ b/aio/tests/deployment/e2e/smoke-tests.e2e-spec.ts @@ -62,7 +62,7 @@ describe(browser.baseUrl, () => { describe('(api docs pages)', () => { const textPerUrl: { [key: string]: string } = { - /* Class */ 'api/core/Injector-0': 'class injector', + /* Class */ 'api/core/Injector': 'class injector', /* Const */ 'api/forms/NG_VALIDATORS': 'const ng_validators', /* Decorator */ 'api/core/Component': '@component', /* Directive */ 'api/common/NgIf': 'class ngif', diff --git a/aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.js b/aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.js index 0a070a26be..5c0f4a595d 100644 --- a/aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.js +++ b/aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.js @@ -2,7 +2,7 @@ * @dgProcessor disambiguateDocPathsProcessor * @description * - * Ensures that docs that have the same path, other than case changes, + * Ensures that docs that have the same output path, other than case changes, * are disambiguated. * * For example in Angular there is the `ROUTES` const and a `Routes` type. @@ -14,55 +14,30 @@ * ``` * * but in a case-insensitive file-system these two paths point to the same file! + * + * So this processor will encode the paths into lower case that is not affected + * by case-insensitive file-systems. */ -module.exports = function disambiguateDocPathsProcessor(log) { +module.exports = function disambiguateDocPathsProcessor() { return { $runAfter: ['paths-computed'], $runBefore: ['rendering-docs', 'createSitemap'], $process(docs) { - // Collect all the ambiguous docs, whose outputPath is are only different by casing. - const ambiguousDocMap = new Map(); for (const doc of docs) { if (!doc.outputPath) { continue; } - const outputPath = doc.outputPath.toLowerCase(); - if (!ambiguousDocMap.has(outputPath)) { - ambiguousDocMap.set(outputPath, []); - } - const ambiguousDocs = ambiguousDocMap.get(outputPath); - ambiguousDocs.push(doc); - } - - // Create a disambiguator doc for each set of such ambiguous docs, - // and update the ambiguous docs to have unique `path` and `outputPath` properties. - for (const [outputPath, ambiguousDocs] of ambiguousDocMap) { - if (ambiguousDocs.length === 1) { - continue; - } - - log.debug('Docs with ambiguous outputPath:' + ambiguousDocs.map((d, i) => `\n - ${d.id}: "${d.outputPath}" replaced with "${convertPath(d.outputPath, i)}".`)); - - const doc = ambiguousDocs[0]; - const path = doc.path; - const id = `${doc.id.toLowerCase()}-disambiguator`; - const title = `${doc.id.toLowerCase()} (disambiguation)`; - const aliases = [id]; - docs.push({ docType: 'disambiguator', id, title, aliases, path, outputPath, docs: ambiguousDocs }); - - // Update the paths - let count = 0; - for (const doc of ambiguousDocs) { - doc.path = convertPath(doc.path, count); - doc.outputPath = convertPath(doc.outputPath, count); - count += 1; - } + doc.outputPath = encodeToLowercase(doc.outputPath); } } }; }; -function convertPath(path, count) { - // Add the counter before any extension - return path.replace(/(\.[^.]*)?$/, `-${count}$1`); +/** + * To avoid collisions on case-insensitive file-systems, we encode the path to the content in + * a deterministic case-insensitive form - converting all uppercase letters to lowercase followed + * by an underscore. + */ +function encodeToLowercase(str) { + return str.replace(/[A-Z_]/g, char => char.toLowerCase() + '_'); } diff --git a/aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.spec.js b/aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.spec.js index 22eae728c1..45691df274 100644 --- a/aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.spec.js +++ b/aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.spec.js @@ -9,12 +9,14 @@ describe('disambiguateDocPaths processor', () => { injector = dgeni.configureInjector(); processor = injector.get('disambiguateDocPathsProcessor'); docs = [ - {docType: 'test-doc', id: 'test-doc', path: 'test/doc', outputPath: 'test/doc.json'}, - {docType: 'test-doc', id: 'TEST-DOC', path: 'TEST/DOC', outputPath: 'TEST/DOC.json'}, - {docType: 'test-doc', id: 'test-Doc', path: 'test/Doc', outputPath: 'test/Doc.xml'}, - {docType: 'test-doc', id: 'unique-doc', path: 'unique/doc', outputPath: 'unique/doc.json'}, - {docType: 'test-doc', id: 'other-doc', path: 'other/doc', outputPath: 'other/doc.json'}, - {docType: 'test-doc', id: 'other-DOC', path: 'other/DOC', outputPath: 'other/DOC.json'}, + { docType: 'test-doc', id: 'test-doc', path: 'test/doc', outputPath: 'test/doc.json' }, + { docType: 'test-doc', id: 'TEST-DOC', path: 'TEST/DOC', outputPath: 'TEST/DOC.json' }, + { docType: 'test-doc', id: 'test-Doc', path: 'test/Doc', outputPath: 'test/Doc.xml' }, + { docType: 'test-doc', id: 'unique-doc', path: 'unique/doc', outputPath: 'unique/doc.json' }, + { docType: 'test-doc', id: 'other-doc', path: 'other/doc', outputPath: 'other/doc.json' }, + { docType: 'test-doc', id: 'other-DOC', path: 'other/DOC', outputPath: 'other/DOC.json' }, + { docType: 'test-doc', id: 'has_underscore', path: 'has_underscore', outputPath: 'has_underscore.json' }, + { docType: 'test-doc', id: 'HAS_UNDERSCORE', path: 'HAS_UNDERSCORE', outputPath: 'HAS_UNDERSCORE.json' }, ]; }); @@ -26,44 +28,23 @@ describe('disambiguateDocPaths processor', () => { expect(processor.$runBefore).toContain('createSitemap'); }); - it('should create `disambiguator` documents for docs that have ambiguous outputPaths', () => { - const numDocs = docs.length; + it('should update the path and outputPath properties of each doc to be unambiguous on case-insensitive file-systems', () => { processor.$process(docs); - expect(docs.length).toEqual(numDocs + 2); - expect(docs[docs.length - 2]).toEqual({ - docType: 'disambiguator', - id: 'test-doc-disambiguator', - title: 'test-doc (disambiguation)', - aliases: ['test-doc-disambiguator'], - path: 'test/doc', - outputPath: 'test/doc.json', - docs: [docs[0], docs[1]], - }); - expect(docs[docs.length - 1]).toEqual({ - docType: 'disambiguator', - id: 'other-doc-disambiguator', - title: 'other-doc (disambiguation)', - aliases: ['other-doc-disambiguator'], - path: 'other/doc', - outputPath: 'other/doc.json', - docs: [docs[4], docs[5]], - }); - }); - - it('should update the path and outputPath properties of each ambiguous doc', () => { - processor.$process(docs); - expect(docs[0].path).toEqual('test/doc-0'); - expect(docs[0].outputPath).toEqual('test/doc-0.json'); - expect(docs[1].path).toEqual('TEST/DOC-1'); - expect(docs[1].outputPath).toEqual('TEST/DOC-1.json'); - - // The non-ambiguous docs are left alone - expect(docs[2].outputPath).toEqual('test/Doc.xml'); + expect(docs[0].path).toEqual('test/doc'); + expect(docs[0].outputPath).toEqual('test/doc.json'); + expect(docs[1].path).toEqual('TEST/DOC'); + expect(docs[1].outputPath).toEqual('t_e_s_t_/d_o_c_.json'); + expect(docs[2].path).toEqual('test/Doc'); + expect(docs[2].outputPath).toEqual('test/d_oc.xml'); + expect(docs[3].path).toEqual('unique/doc'); expect(docs[3].outputPath).toEqual('unique/doc.json'); - - expect(docs[4].path).toEqual('other/doc-0'); - expect(docs[4].outputPath).toEqual('other/doc-0.json'); - expect(docs[5].path).toEqual('other/DOC-1'); - expect(docs[5].outputPath).toEqual('other/DOC-1.json'); + expect(docs[4].path).toEqual('other/doc'); + expect(docs[4].outputPath).toEqual('other/doc.json'); + expect(docs[5].path).toEqual('other/DOC'); + expect(docs[5].outputPath).toEqual('other/d_o_c_.json'); + expect(docs[6].path).toEqual('has_underscore'); + expect(docs[6].outputPath).toEqual('has__underscore.json'); + expect(docs[7].path).toEqual('HAS_UNDERSCORE'); + expect(docs[7].outputPath).toEqual('h_a_s___u_n_d_e_r_s_c_o_r_e_.json'); }); }); diff --git a/aio/tools/transforms/authors-package/index.spec.js b/aio/tools/transforms/authors-package/index.spec.js index 440bc400c1..525ea7a702 100644 --- a/aio/tools/transforms/authors-package/index.spec.js +++ b/aio/tools/transforms/authors-package/index.spec.js @@ -71,14 +71,14 @@ describe('authors-package (integration tests)', () => { it('should generate API doc if the "fileChanged" is an API doc', () => { return generateDocs('packages/forms/src/form_builder.ts', { silent: true }).then(() => { expect(fs.writeFile).toHaveBeenCalled(); - expect(files).toContain(resolve(DOCS_OUTPUT_PATH, 'api/forms/FormBuilder.json')); + expect(files).toContain(resolve(DOCS_OUTPUT_PATH, 'api/forms/f_ormb_uilder.json')); }); }, 16000); it('should generate API doc if the "fileChanged" is an API example', () => { return generateDocs('packages/examples/forms/ts/formBuilder/form_builder_example.ts', { silent: true }).then(() => { expect(fs.writeFile).toHaveBeenCalled(); - expect(files).toContain(resolve(DOCS_OUTPUT_PATH, 'api/forms/FormBuilder.json')); + expect(files).toContain(resolve(DOCS_OUTPUT_PATH, 'api/forms/f_ormb_uilder.json')); }); }, 16000); }); diff --git a/goldens/size-tracking/aio-payloads.json b/goldens/size-tracking/aio-payloads.json index 4dd3b70d74..53b1d231a8 100755 --- a/goldens/size-tracking/aio-payloads.json +++ b/goldens/size-tracking/aio-payloads.json @@ -3,7 +3,7 @@ "master": { "uncompressed": { "runtime-es2017": 4619, - "main-es2017": 456795, + "main-es2017": 456578, "polyfills-es2017": 55210 } }