From d1f4bc15b786b90833ba33f0cafe9fd46f96f075 Mon Sep 17 00:00:00 2001 From: Brandon Stirnaman Date: Thu, 8 Jun 2017 09:38:48 -0400 Subject: [PATCH 01/10] Switch to using child routes --- lib/sky-pages-route-generator.js | 62 +++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/lib/sky-pages-route-generator.js b/lib/sky-pages-route-generator.js index 9a2d51e3..0e632744 100644 --- a/lib/sky-pages-route-generator.js +++ b/lib/sky-pages-route-generator.js @@ -123,22 +123,60 @@ function generateDefinitions(routes) { return routes.map(route => route.componentDefinition).join('\n\n'); } +function generateRouteDeclaration(route) { + let guard = route.guard ? route.guard.name : ''; + let childRoutes = ''; + route.children.forEach(child => childRoutes += generateRouteDeclaration(child)); + + return `{ + path: '${route.routePath}', + component: ${route.componentName}, + canActivate: [${guard}], + canDeactivate: [${guard}], + canActivateChild: [${guard}], + children: [${childRoutes}] + }`; +} + +function mapRoutes(routes, mappedRoutes = [], routeMap = {}) { + const unmappedRoutes = []; + + routes.forEach(route => { + if (route.routePath.indexOf('/') === -1) { + mappedRoutes.push(route); + routeMap[route.routePath] = route; + route.children = []; + } else { + const routeParentPath = route.routePath.substring(0, route.routePath.lastIndexOf('/')); + const routeParent = routeMap[routeParentPath]; + if (routeParent) { + routeParent.children.push(route); + routeMap[route.routePath] = route; + route.routePath = route.routePath.substring(routeParentPath.length + 1); + route.children = []; + } else { + unmappedRoutes.push(route); + } + } + }); + + // if we have managed to map at least one route + // but still have unmapped routes, recurse! + if (mappedRoutes.length > 1 && unmappedRoutes.length > 0) { + return mapRoutes(routes, mappedRoutes, routeMap); + } + + return mappedRoutes; +} + function generateDeclarations(routes) { const p = indent(1); - const declarations = routes - .map(r => { - let guard = r.guard ? r.guard.name : ''; - let declaration = -`${p}{ - path: '${r.routePath}', - component: ${r.componentName}, - canActivate: [${guard}], - canDeactivate: [${guard}] -}`; - return declaration; - }) + const mappedRoutes = mapRoutes(routes); + const declarations = mappedRoutes + .map(r => generateRouteDeclaration(r)) .join(',\n'); + return `[\n${declarations}\n]`; } From 5e8213dae1c10c54c00ced4e58e691531296f6dc Mon Sep 17 00:00:00 2001 From: Brandon Stirnaman Date: Fri, 9 Jun 2017 11:21:28 -0400 Subject: [PATCH 02/10] Add support for child routes and child guards --- lib/sky-pages-route-generator.js | 130 ++++++++++++++++++++++++------- 1 file changed, 101 insertions(+), 29 deletions(-) diff --git a/lib/sky-pages-route-generator.js b/lib/sky-pages-route-generator.js index 0e632744..2301910f 100644 --- a/lib/sky-pages-route-generator.js +++ b/lib/sky-pages-route-generator.js @@ -106,6 +106,14 @@ function generateRoutes(skyAppConfig) { .sync(path.join(skyAppConfig.runtime.srcPath, skyAppConfig.runtime.routesPattern)) .map(file => parseFileIntoEntity(skyAppConfig, file, counter++)); + // Add a root component that will become the wrapper for all the others + entities.push({ + routePath: ['~'], // we need a non-empty route path so it won't be merged later + componentName: 'RootComponent', + componentDefinition: + `@Component({ template: '' }) export class RootComponent {}` + }); + if (skyAppConfig.runtime.handle404) { const err = ``; entities.push({ @@ -124,9 +132,10 @@ function generateDefinitions(routes) { } function generateRouteDeclaration(route) { - let guard = route.guard ? route.guard.name : ''; - let childRoutes = ''; - route.children.forEach(child => childRoutes += generateRouteDeclaration(child)); + const guard = route.guard ? route.guard.name : ''; + const childRoutes = route.children + .map(child => generateRouteDeclaration(child)) + .join(',\n'); return `{ path: '${route.routePath}', @@ -138,42 +147,105 @@ function generateRouteDeclaration(route) { }`; } -function mapRoutes(routes, mappedRoutes = [], routeMap = {}) { - const unmappedRoutes = []; - - routes.forEach(route => { - if (route.routePath.indexOf('/') === -1) { - mappedRoutes.push(route); - routeMap[route.routePath] = route; - route.children = []; - } else { - const routeParentPath = route.routePath.substring(0, route.routePath.lastIndexOf('/')); - const routeParent = routeMap[routeParentPath]; - if (routeParent) { - routeParent.children.push(route); - routeMap[route.routePath] = route; - route.routePath = route.routePath.substring(routeParentPath.length + 1); - route.children = []; +function parseRoute(route) { + let result; + route.routePath = (route.routePath || '').toString(); + + const routeTokens = route.routePath.split('/'); + const lastToken = routeTokens[routeTokens.length - 1]; + + if (lastToken.startsWith('#')) { + const reversedTokens = routeTokens.slice().reverse(); + const childTokens = [lastToken]; + for (let i = 1; i < reversedTokens.length; i++) { + let token = reversedTokens[i]; + if (token.startsWith('#')) { + childTokens.push(token); } else { - unmappedRoutes.push(route); + break; } } - }); - // if we have managed to map at least one route - // but still have unmapped routes, recurse! - if (mappedRoutes.length > 1 && unmappedRoutes.length > 0) { - return mapRoutes(routes, mappedRoutes, routeMap); + result = { + routePath: routeTokens.slice(0, routeTokens.length - childTokens.length).join('/'), + children: [] + }; + + let currentRoute = result; + childTokens.reverse().forEach(token => { + let childToken = { + routePath: token.substring(1), + children: [] + }; + + if (token === lastToken) { + childToken.componentName = route.componentName; + childToken.guard = route.guard; + } + + currentRoute.children.push(childToken); + currentRoute = childToken; + }); + } else { + result = { + routePath: route.routePath, + guard: route.guard, + componentName: route.componentName, + children: [] + }; } - return mappedRoutes; + result.routePath = result.routePath.replace(/\#/g, ''); + return result; +} + +function mergeRoutes(routes) { + const routeIndex = {}; + const uniqueRoutes = []; + + routes.forEach(route => { + const existingRoute = routeIndex[route.routePath]; + if (existingRoute) { + route.children.forEach(rc => existingRoute.children.push(rc)); + existingRoute.children = mergeRoutes(existingRoute.children); + + if (route.componentName) { + existingRoute.componentName = route.componentName; + } + + if (route.guard) { + existingRoute.guard = route.guard; + } + } else { + routeIndex[route.routePath] = route; + uniqueRoutes.push(route); + } + }); + + return uniqueRoutes; } function generateDeclarations(routes) { - const p = indent(1); + let mappedRoutes = mergeRoutes(routes.map(r => parseRoute(r))); + + // nest all routes under a top-level route to allow for app-wide guard + // steal guard from app/index component, if exists + const baseRoutes = mappedRoutes.filter(e => e.routePath === '~' || e.routePath === '**'); + const rootRoute = baseRoutes[0]; + + const indexRoute = mappedRoutes.filter(e => e.routePath === '' && e.guard)[0]; + if (indexRoute) { + rootRoute.guard = indexRoute.guard; + indexRoute.guard = null; + } + + mappedRoutes + .filter(e => e.routePath !== '~' && e.routePath !== '**') + .forEach(e => rootRoute.children.push(e)); - const mappedRoutes = mapRoutes(routes); - const declarations = mappedRoutes + // reset root route path to '' + rootRoute.routePath = ''; + const declarations = baseRoutes .map(r => generateRouteDeclaration(r)) .join(',\n'); From c6a2bfb1628dbe38de0bdc2a9f96527bc59f5057 Mon Sep 17 00:00:00 2001 From: Brandon Stirnaman Date: Fri, 9 Jun 2017 11:33:43 -0400 Subject: [PATCH 03/10] Fix an issue with guards being applied incorrectly --- lib/sky-pages-route-generator.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/sky-pages-route-generator.js b/lib/sky-pages-route-generator.js index 2301910f..0d91f4d8 100644 --- a/lib/sky-pages-route-generator.js +++ b/lib/sky-pages-route-generator.js @@ -132,7 +132,6 @@ function generateDefinitions(routes) { } function generateRouteDeclaration(route) { - const guard = route.guard ? route.guard.name : ''; const childRoutes = route.children .map(child => generateRouteDeclaration(child)) .join(',\n'); @@ -140,9 +139,9 @@ function generateRouteDeclaration(route) { return `{ path: '${route.routePath}', component: ${route.componentName}, - canActivate: [${guard}], - canDeactivate: [${guard}], - canActivateChild: [${guard}], + canActivate: [${route.guard && route.guard.canActivate ? route.guard.name : ''}], + canDeactivate: [${route.guard && route.guard.canDeactivate ? route.guard.name : ''}], + canActivateChild: [${route.guard && route.guard.canActivateChild ? route.guard.name : ''}], children: [${childRoutes}] }`; } @@ -301,7 +300,13 @@ function extractGuard(file) { throw new Error(`As a best practice, only export one guard per file in ${file}`); } - result = { path: file.replace(/\\/g, '/'), name: match[1] }; + result = { + path: file.replace(/\\/g, '/'), + name: match[1], + canActivate: content.match(/canActivate\s*\(/g) !== null, + canDeactivate: content.match(/canDeactivate\s*\(/g) !== null, + canActivateChild: content.match(/canActivateChild\s*\(/g) !== null + }; } return result; From 98b4d0746aec4f1af1d1c0f7ed0d8d662244a8de Mon Sep 17 00:00:00 2001 From: Brandon Stirnaman Date: Fri, 9 Jun 2017 11:41:47 -0400 Subject: [PATCH 04/10] Fix broken test --- test/sky-pages-route-generator.spec.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/sky-pages-route-generator.spec.js b/test/sky-pages-route-generator.spec.js index 713746ba..c7915c36 100644 --- a/test/sky-pages-route-generator.spec.js +++ b/test/sky-pages-route-generator.spec.js @@ -141,7 +141,11 @@ describe('SKY UX Builder route generator', () => { it('should support guards with custom routesPattern', () => { spyOn(glob, 'sync').and.callFake(() => ['my-custom-src/my-custom-route/index.html']); - spyOn(fs, 'readFileSync').and.returnValue('@Injectable() export class Guard {}'); + spyOn(fs, 'readFileSync').and.returnValue(`@Injectable() export class Guard { + canActivate() {} + canDeactivate() {} + canActivateChild() {} + }`); spyOn(fs, 'existsSync').and.returnValue(true); let routes = generator.getRoutes({ @@ -159,6 +163,10 @@ describe('SKY UX Builder route generator', () => { `canDeactivate: [Guard]` ); + expect(routes.declarations).toContain( + `canActivateChild: [Guard]` + ); + expect(routes.providers).toContain( `Guard` ); From 55ed0003e09a7bb41658a0a168628fae877d4334 Mon Sep 17 00:00:00 2001 From: Brandon Stirnaman Date: Fri, 9 Jun 2017 11:54:32 -0400 Subject: [PATCH 05/10] Clean up routes for SkyAppConfig --- lib/sky-pages-route-generator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sky-pages-route-generator.js b/lib/sky-pages-route-generator.js index 0d91f4d8..5c71aabd 100644 --- a/lib/sky-pages-route-generator.js +++ b/lib/sky-pages-route-generator.js @@ -272,7 +272,7 @@ function generateNames(routes) { // Specifically routeDefinition caused errors for skyux e2e in Windows. function getRoutesForConfig(routes) { return routes.map(route => ({ - routePath: route.routePath, + routePath: route.routePath === '~' ? '' : route.routePath.replace(/\#/g, ''), routeParams: route.routeParams })); } From 4b97be0aafb188431b63e3c53a9cbef352c97f63 Mon Sep 17 00:00:00 2001 From: Brandon Stirnaman Date: Fri, 9 Jun 2017 12:07:20 -0400 Subject: [PATCH 06/10] Add unit tests for new routing --- test/sky-pages-route-generator.spec.js | 100 +++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/test/sky-pages-route-generator.spec.js b/test/sky-pages-route-generator.spec.js index c7915c36..3647299f 100644 --- a/test/sky-pages-route-generator.spec.js +++ b/test/sky-pages-route-generator.spec.js @@ -188,4 +188,104 @@ describe('SKY UX Builder route generator', () => { } })).toThrow(new Error(`As a best practice, only export one guard per file in ${file}`)); }); + + it('should handle top-level routes', () => { + spyOn(glob, 'sync').and.callFake(() => ['my-custom-src/my-custom-route/index.html']); + spyOn(path, 'join').and.returnValue(''); + const routes = generator.getRoutes({ + runtime: { + srcPath: '' + } + }); + + expect(routes.declarations).toContain( + `path: 'my-custom-src/my-custom-route'` + ); + }); + + it('should handle child routes', () => { + spyOn(glob, 'sync').and.callFake(() => ['my-custom-src/#my-custom-route/index.html']); + spyOn(path, 'join').and.returnValue(''); + const routes = generator.getRoutes({ + runtime: { + srcPath: '' + } + }); + + expect(routes.declarations).toContain( + `path: 'my-custom-src'` + ); + + expect(routes.declarations).toContain( + `path: 'my-custom-route'` + ); + }); + + it('should handle nested child routes', () => { + spyOn(glob, 'sync').and.callFake(() => ['my-custom-src/#my-custom-route/#nested/index.html']); + spyOn(path, 'join').and.returnValue(''); + const routes = generator.getRoutes({ + runtime: { + srcPath: '' + } + }); + + expect(routes.declarations).toContain( + `path: 'my-custom-src'` + ); + + expect(routes.declarations).toContain( + `path: 'my-custom-route'` + ); + + expect(routes.declarations).toContain( + `path: 'nested'` + ); + }); + + it('should merge child routes when necessary', () => { + spyOn(glob, 'sync').and.callFake(() => [ + 'my-custom-src/#my-custom-route/#nested/index.html', + 'my-custom-src/#my-custom-route/index.html', + '' + ]); + spyOn(fs, 'readFileSync').and.returnValue(`@Injectable() export class Guard { + canActivate() {} + canDeactivate() {} + canActivateChild() {} + }`); + spyOn(fs, 'existsSync').and.returnValue(true); + spyOn(path, 'join').and.returnValue(''); + const routes = generator.getRoutes({ + runtime: { + srcPath: '' + } + }); + + expect(routes.declarations).toContain( + `path: 'my-custom-src'` + ); + + expect(routes.declarations).toContain( + `path: 'my-custom-route'` + ); + + expect(routes.declarations).toContain( + `path: 'nested'` + ); + }); + + it('should handle top-level routes within a child route', () => { + spyOn(glob, 'sync').and.callFake(() => ['my-custom-src/#my-custom-route/top-level/index.html']); + spyOn(path, 'join').and.returnValue(''); + const routes = generator.getRoutes({ + runtime: { + srcPath: '' + } + }); + + expect(routes.declarations).toContain( + `path: 'my-custom-src/my-custom-route/top-level'` + ); + }); }); From 194255a6855715f16597e8ea7825f6064c304d0f Mon Sep 17 00:00:00 2001 From: Brandon Stirnaman Date: Fri, 9 Jun 2017 15:38:50 -0400 Subject: [PATCH 07/10] Add e2e tests, improve unit test for route merging --- e2e/shared/common.js | 32 +++++++++++- e2e/shared/tests.js | 36 +++++++++++++- e2e/skyux-build-aot.e2e-spec.js | 69 ++++++++++++++++++++++++++ e2e/skyux-build-jit.e2e-spec.js | 69 ++++++++++++++++++++++++++ test/sky-pages-route-generator.spec.js | 12 ++--- 5 files changed, 208 insertions(+), 10 deletions(-) diff --git a/e2e/shared/common.js b/e2e/shared/common.js index 98066f93..d73a8ca4 100644 --- a/e2e/shared/common.js +++ b/e2e/shared/common.js @@ -211,6 +211,34 @@ function writeAppFile(filePath, content) { }); } +/** + * Verify directory exists in src/app folder + */ +function verifyAppFolder(folderPath) { + const resolvedFolderPath = path.join(path.resolve(tmp), 'src', 'app', folderPath); + return new Promise((resolve, reject) => { + if (!fs.existsSync(resolvedFolderPath)) { + fs.mkdirSync(resolvedFolderPath); + } + + resolve(); + }); +} + +/** + * Verify directory exists in src/app folder + */ +function removeAppFolder(folderPath) { + const resolvedFolderPath = path.join(path.resolve(tmp), 'src', 'app', folderPath); + return new Promise((resolve, reject) => { + if (fs.existsSync(resolvedFolderPath)) { + fs.rmdirSync(resolvedFolderPath); + } + + resolve(); + }); +} + /** * Remove file from the src/app folder -- Used for cleaning up after we've injected * files for a specific test or group of tests @@ -242,5 +270,7 @@ module.exports = { prepareServe: prepareServe, tmp: tmp, writeAppFile: writeAppFile, - removeAppFile: removeAppFile + removeAppFile: removeAppFile, + verifyAppFolder: verifyAppFolder, + removeAppFolder: removeAppFolder }; diff --git a/e2e/shared/tests.js b/e2e/shared/tests.js index 2cb947f4..8cedfb0a 100644 --- a/e2e/shared/tests.js +++ b/e2e/shared/tests.js @@ -1,5 +1,5 @@ /*jshint jasmine: true, node: true */ -/*global element, by, $$*/ +/*global element, by, $$, protractor, browser*/ 'use strict'; const fs = require('fs'); @@ -52,6 +52,40 @@ module.exports = { const nav = $$('.sky-navbar-item a'); nav.get(1).click(); expect(element(by.tagName('h1')).getText()).toBe('SKY UX Template'); + + const aboutComponent = $$('my-about')[0]; + expect(aboutComponent).toBe(undefined); + + done(); + }, + + respectRootGuard: (done) => { + // if the home component isn't there, the outlet was not + // allowed to activate due to the Guard! + const homeComponent = $$('my-home')[0]; + expect(homeComponent).toBe(undefined); + done(); + }, + + verifyChildRoute: (done) => { + $$('#test').get(0).click(); + expect($$('h1').get(0).getText()).toBe('Hi'); + done(); + }, + + verifyNestedChildRoute: (done) => { + $$('#child').get(0).click(); + + expect($$('h1').get(0).getText()).toBe('Hi'); + expect($$('#text').get(0).getText()).toBe('Child'); + done(); + }, + + verifyNestedTopRoute: (done) => { + $$('#top').get(0).click(); + + expect($$('h1')[0]).toBe(undefined); + expect($$('#text').get(0).getText()).toBe('Top'); done(); } }; diff --git a/e2e/skyux-build-aot.e2e-spec.js b/e2e/skyux-build-aot.e2e-spec.js index 9fc82860..6beb7477 100644 --- a/e2e/skyux-build-aot.e2e-spec.js +++ b/e2e/skyux-build-aot.e2e-spec.js @@ -54,4 +54,73 @@ export class AboutGuard { .catch(console.error); }); }); + + describe('w/root level guard', () => { + beforeAll((done) => { + const guard = ` +import { Injectable } from '@angular/core'; + +@Injectable() +export class RootGuard { + canActivateChild(next: any, state: any) { + return false; + } +} +`; + + common.writeAppFile('index.guard.ts', guard) + .then(() => prepareBuild()) + .then(done) + .catch(console.error); + }); + + it('should respect root guard', tests.respectRootGuard); + + afterAll((done) => { + common.removeAppFile('index.guard.ts') + .then(() => common.afterAll()) + .then(done) + .catch(console.error); + }); + }); + + describe('w/child routes', () => { + beforeAll((done) => { + common.verifyAppFolder('test') + .then(() => common.writeAppFile('index.html', 'Test')) + .then(() => common.writeAppFile( + 'test/index.html', + '

Hi

' + + 'Child' + + 'Top' + + '') + ) + .then(() => common.verifyAppFolder('test/#child')) + .then(() => common.writeAppFile('test/#child/index.html', '
Child
')) + .then(() => common.verifyAppFolder('test/#child/top')) + .then(() => common.writeAppFile('test/#child/top/index.html', '
Top
')) + .then(() => prepareBuild()) + .then(done) + .catch(console.error); + }); + + it('should have working child route', tests.verifyChildRoute); + + it('should have working nested child route', tests.verifyNestedChildRoute); + + it('should have working top level route inside child route folder', tests.verifyNestedTopRoute); + + afterAll((done) => { + common.removeAppFile('test/#child/top/index.html') + .then(() => common.writeAppFile('index.html', '')) + .then(() => common.removeAppFile('test/#child/index.html')) + .then(() => common.removeAppFile('test/index.html')) + .then(() => common.removeAppFolder('test/#child/top')) + .then(() => common.removeAppFolder('test/#child')) + .then(() => common.removeAppFolder('test')) + .then(() => common.afterAll()) + .then(done) + .catch(console.error); + }); + }); }); diff --git a/e2e/skyux-build-jit.e2e-spec.js b/e2e/skyux-build-jit.e2e-spec.js index a1dbcf69..714ecb85 100644 --- a/e2e/skyux-build-jit.e2e-spec.js +++ b/e2e/skyux-build-jit.e2e-spec.js @@ -54,4 +54,73 @@ export class AboutGuard { .catch(console.error); }); }); + + describe('w/root level guard', () => { + beforeAll((done) => { + const guard = ` +import { Injectable } from '@angular/core'; + +@Injectable() +export class RootGuard { + canActivateChild(next: any, state: any) { + return false; + } +} +`; + + common.writeAppFile('index.guard.ts', guard) + .then(() => prepareBuild()) + .then(done) + .catch(console.error); + }); + + it('should respect root guard', tests.respectRootGuard); + + afterAll((done) => { + common.removeAppFile('index.guard.ts') + .then(() => common.afterAll()) + .then(done) + .catch(console.error); + }); + }); + + describe('w/child routes', () => { + beforeAll((done) => { + common.verifyAppFolder('test') + .then(() => common.writeAppFile('index.html', 'Test')) + .then(() => common.writeAppFile( + 'test/index.html', + '

Hi

' + + 'Child' + + 'Top' + + '') + ) + .then(() => common.verifyAppFolder('test/#child')) + .then(() => common.writeAppFile('test/#child/index.html', '
Child
')) + .then(() => common.verifyAppFolder('test/#child/top')) + .then(() => common.writeAppFile('test/#child/top/index.html', '
Top
')) + .then(() => prepareBuild()) + .then(done) + .catch(console.error); + }); + + it('should have working child route', tests.verifyChildRoute); + + it('should have working nested child route', tests.verifyNestedChildRoute); + + it('should have working top level route inside child route folder', tests.verifyNestedTopRoute); + + afterAll((done) => { + common.removeAppFile('test/#child/top/index.html') + .then(() => common.writeAppFile('index.html', '')) + .then(() => common.removeAppFile('test/#child/index.html')) + .then(() => common.removeAppFile('test/index.html')) + .then(() => common.removeAppFolder('test/#child/top')) + .then(() => common.removeAppFolder('test/#child')) + .then(() => common.removeAppFolder('test')) + .then(() => common.afterAll()) + .then(done) + .catch(console.error); + }); + }); }); diff --git a/test/sky-pages-route-generator.spec.js b/test/sky-pages-route-generator.spec.js index 3647299f..9745abfa 100644 --- a/test/sky-pages-route-generator.spec.js +++ b/test/sky-pages-route-generator.spec.js @@ -230,14 +230,10 @@ describe('SKY UX Builder route generator', () => { } }); - expect(routes.declarations).toContain( - `path: 'my-custom-src'` - ); - - expect(routes.declarations).toContain( - `path: 'my-custom-route'` - ); - + // expect only one instance each of `my-custom-src` and `my-custom-route` + // in the declarations + expect(routes.declarations.match(/path\:\s\'my\-custom\-src\'/g).length).toBe(1); + expect(routes.declarations.match(/path\:\s\'my\-custom\-route\'/g).length).toBe(1); expect(routes.declarations).toContain( `path: 'nested'` ); From d4cce662490bfc1c05e0ba1d70ed42f9daf0ef87 Mon Sep 17 00:00:00 2001 From: Brandon Stirnaman Date: Tue, 13 Jun 2017 11:00:06 -0500 Subject: [PATCH 08/10] Switch to less-likely to be used character than ~ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ‘?’ is an illegal character for windows paths and incredibly unlikely to be used. --- lib/sky-pages-route-generator.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/sky-pages-route-generator.js b/lib/sky-pages-route-generator.js index 5c71aabd..fec991bc 100644 --- a/lib/sky-pages-route-generator.js +++ b/lib/sky-pages-route-generator.js @@ -108,7 +108,7 @@ function generateRoutes(skyAppConfig) { // Add a root component that will become the wrapper for all the others entities.push({ - routePath: ['~'], // we need a non-empty route path so it won't be merged later + routePath: ['?'], // we need a non-empty route path so it won't be merged later componentName: 'RootComponent', componentDefinition: `@Component({ template: '' }) export class RootComponent {}` @@ -229,7 +229,7 @@ function generateDeclarations(routes) { // nest all routes under a top-level route to allow for app-wide guard // steal guard from app/index component, if exists - const baseRoutes = mappedRoutes.filter(e => e.routePath === '~' || e.routePath === '**'); + const baseRoutes = mappedRoutes.filter(e => e.routePath === '?' || e.routePath === '**'); const rootRoute = baseRoutes[0]; const indexRoute = mappedRoutes.filter(e => e.routePath === '' && e.guard)[0]; @@ -239,7 +239,7 @@ function generateDeclarations(routes) { } mappedRoutes - .filter(e => e.routePath !== '~' && e.routePath !== '**') + .filter(e => e.routePath !== '?' && e.routePath !== '**') .forEach(e => rootRoute.children.push(e)); // reset root route path to '' @@ -272,7 +272,7 @@ function generateNames(routes) { // Specifically routeDefinition caused errors for skyux e2e in Windows. function getRoutesForConfig(routes) { return routes.map(route => ({ - routePath: route.routePath === '~' ? '' : route.routePath.replace(/\#/g, ''), + routePath: route.routePath === '?' ? '' : route.routePath.replace(/\#/g, ''), routeParams: route.routeParams })); } From 995a9fa488726473237ee41166111ff7076715fc Mon Sep 17 00:00:00 2001 From: Brandon Stirnaman Date: Tue, 13 Jun 2017 11:00:14 -0500 Subject: [PATCH 09/10] Fix incorrect comment --- e2e/shared/common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/shared/common.js b/e2e/shared/common.js index d73a8ca4..35886b03 100644 --- a/e2e/shared/common.js +++ b/e2e/shared/common.js @@ -226,7 +226,7 @@ function verifyAppFolder(folderPath) { } /** - * Verify directory exists in src/app folder + * Remove directory if it exists in src/app folder */ function removeAppFolder(folderPath) { const resolvedFolderPath = path.join(path.resolve(tmp), 'src', 'app', folderPath); From 7a107830b8ee519e886879f1c22a75ddf60abe8c Mon Sep 17 00:00:00 2001 From: Brandon Stirnaman Date: Tue, 13 Jun 2017 11:06:53 -0500 Subject: [PATCH 10/10] Add some strategic comments to avoid :predicament: --- lib/sky-pages-route-generator.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/sky-pages-route-generator.js b/lib/sky-pages-route-generator.js index fec991bc..1faf54ed 100644 --- a/lib/sky-pages-route-generator.js +++ b/lib/sky-pages-route-generator.js @@ -153,6 +153,7 @@ function parseRoute(route) { const routeTokens = route.routePath.split('/'); const lastToken = routeTokens[routeTokens.length - 1]; + // if it begins with #, that indicates we should create a child route if (lastToken.startsWith('#')) { const reversedTokens = routeTokens.slice().reverse(); const childTokens = [lastToken]; @@ -165,11 +166,13 @@ function parseRoute(route) { } } + // calculate the top level portion of the route, excluding child routes result = { routePath: routeTokens.slice(0, routeTokens.length - childTokens.length).join('/'), children: [] }; + // traverse child tokens and create the child routes let currentRoute = result; childTokens.reverse().forEach(token => { let childToken = { @@ -186,6 +189,7 @@ function parseRoute(route) { currentRoute = childToken; }); } else { + // top level route, just add it result = { routePath: route.routePath, guard: route.guard, @@ -194,6 +198,7 @@ function parseRoute(route) { }; } + // strip # characters out of routes result.routePath = result.routePath.replace(/\#/g, ''); return result; } @@ -203,6 +208,8 @@ function mergeRoutes(routes) { const uniqueRoutes = []; routes.forEach(route => { + // if route already exists, recursively merge its children as well + // as its guard and componentName properties const existingRoute = routeIndex[route.routePath]; if (existingRoute) { route.children.forEach(rc => existingRoute.children.push(rc)); @@ -232,12 +239,14 @@ function generateDeclarations(routes) { const baseRoutes = mappedRoutes.filter(e => e.routePath === '?' || e.routePath === '**'); const rootRoute = baseRoutes[0]; + // Look for index route, 'steal' its guard for the root route if available const indexRoute = mappedRoutes.filter(e => e.routePath === '' && e.guard)[0]; if (indexRoute) { rootRoute.guard = indexRoute.guard; indexRoute.guard = null; } + // push the non-root routes into root child collection mappedRoutes .filter(e => e.routePath !== '?' && e.routePath !== '**') .forEach(e => rootRoute.children.push(e));