Skip to content

Commit 6ab0cd5

Browse files
jgw96danbucholtz
authored andcommitted
feature(generators): generate pages, tabs, components, directives, pipes, providers, etc from app-scripts (integrates with CLI 3.0)
* chore(generators): wip: tabs generator * chore(generators): suffix for tabs was wrong * test(util): fix bug with replaceAll * chore(generators): support TAB_CONTENT and TAB_VARIABLES * chore(generators): directives go in directives * chore(generators): test TAB_CONTENT and TAB_VARIABLES replacement * fix(generators): do not put a new line on the last tab * chore(generators): tabs now import first tab into tabs ngModule * refactor(generators): split tab module manipulation into a util function * chore(generators): should return * test(generators): add tests for tabs ngModule manipulation * chore(generators): add correct type * test(generators): add tests for nonPageFileManipulation
1 parent b4c287a commit 6ab0cd5

File tree

6 files changed

+204
-51
lines changed

6 files changed

+204
-51
lines changed

src/generators.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
1-
import { generateContext } from './util/config';
21
import * as Constants from './util/constants';
32
import { BuildContext } from './util/interfaces';
4-
import { getNgModules, GeneratorOption, GeneratorRequest, nonPageFileManipulation, processNonTabRequest } from './generators/util';
3+
import { hydrateRequest, hydrateTabRequest, getNgModules, GeneratorOption, GeneratorRequest, nonPageFileManipulation, generateTemplates, tabsModuleManipulation } from './generators/util';
54

65
export { getNgModules, GeneratorOption, GeneratorRequest };
76

8-
export function generateNonTab(request: GeneratorRequest) {
9-
const context = generateContext();
10-
return processNonTabRequest(context, request);
11-
}
12-
137
export function processPageRequest(context: BuildContext, name: string) {
14-
return processNonTabRequest(context, { type: 'page', name });
8+
const hydratedRequest = hydrateRequest(context, { type: 'page', name });
9+
return generateTemplates(context, hydratedRequest);
1510
}
1611

1712
export function processPipeRequest(context: BuildContext, name: string, ngModulePath: string) {
@@ -30,6 +25,21 @@ export function processProviderRequest(context: BuildContext, name: string, ngMo
3025
return nonPageFileManipulation(context, name, ngModulePath, 'provider');
3126
}
3227

28+
export function processTabsRequest(context: BuildContext, name: string, tabs: string[]) {
29+
const tabHydratedRequests = tabs.map((tab) => hydrateRequest(context, { type: 'page', name: tab }));
30+
const hydratedRequest = hydrateTabRequest(context, { type: 'tabs', name, tabs: tabHydratedRequests });
31+
32+
return generateTemplates(context, hydratedRequest).then(() => {
33+
const promises = tabHydratedRequests.map((hydratedRequest) => {
34+
return generateTemplates(context, hydratedRequest);
35+
});
36+
37+
return Promise.all(promises);
38+
}).then((tabs) => {
39+
tabsModuleManipulation(tabs, hydratedRequest, tabHydratedRequests);
40+
});
41+
}
42+
3343
export function listOptions() {
3444
const list: GeneratorOption[] = [];
3545
list.push({type: Constants.COMPONENT, multiple: false});

src/generators/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
export const CLASSNAME_VARIABLE = '$CLASSNAME';
2+
export const TAB_CONTENT_VARIABLE = '$TAB_CONTENT';
3+
export const TAB_VARIABLES_VARIABLE = '$TAB_VARIABLES';
24
export const FILENAME_VARIABLE = '$FILENAME';
35
export const SUPPLIEDNAME_VARIABLE = '$SUPPLIEDNAME';
46

src/generators/util.spec.ts

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -222,13 +222,21 @@ export class $CLASSNAMEModule {}
222222
</ion-content>
223223
`;
224224

225+
const fileSeven = '/Users/noone/fileSeven';
226+
const fileSevenContent = `
227+
<ion-tabs>
228+
$TAB_CONTENT
229+
</ion-tabs>
230+
`;
231+
225232
const map = new Map<string, string>();
226233
map.set(fileOne, fileOneContent);
227234
map.set(fileTwo, fileTwoContent);
228235
map.set(fileThree, fileThreeContent);
229236
map.set(fileFour, fileFourContent);
230237
map.set(fileFive, fileFiveContent);
231238
map.set(fileSix, fileSixContent);
239+
map.set(fileSeven, fileSevenContent);
232240

233241
const className = 'SettingsView';
234242
const fileName = 'settings-view';
@@ -241,24 +249,24 @@ export class $CLASSNAMEModule {}
241249
const modifiedContentFour = results.get(fileFour);
242250
const modifiedContentFive = results.get(fileFive);
243251
const modifiedContentSix = results.get(fileSix);
244-
expect(modifiedContentOne.indexOf(GeneratorConstants.CLASSNAME_VARIABLE)).toEqual(-1);
245-
expect(modifiedContentOne.indexOf(GeneratorConstants.FILENAME_VARIABLE)).toEqual(-1);
246-
expect(modifiedContentOne.indexOf(GeneratorConstants.SUPPLIEDNAME_VARIABLE)).toEqual(-1);
247-
expect(modifiedContentTwo.indexOf(GeneratorConstants.CLASSNAME_VARIABLE)).toEqual(-1);
248-
expect(modifiedContentTwo.indexOf(GeneratorConstants.FILENAME_VARIABLE)).toEqual(-1);
249-
expect(modifiedContentTwo.indexOf(GeneratorConstants.SUPPLIEDNAME_VARIABLE)).toEqual(-1);
250-
expect(modifiedContentThree.indexOf(GeneratorConstants.CLASSNAME_VARIABLE)).toEqual(-1);
251-
expect(modifiedContentThree.indexOf(GeneratorConstants.FILENAME_VARIABLE)).toEqual(-1);
252-
expect(modifiedContentThree.indexOf(GeneratorConstants.SUPPLIEDNAME_VARIABLE)).toEqual(-1);
253-
expect(modifiedContentFour.indexOf(GeneratorConstants.CLASSNAME_VARIABLE)).toEqual(-1);
254-
expect(modifiedContentFour.indexOf(GeneratorConstants.FILENAME_VARIABLE)).toEqual(-1);
255-
expect(modifiedContentFour.indexOf(GeneratorConstants.SUPPLIEDNAME_VARIABLE)).toEqual(-1);
256-
expect(modifiedContentFive.indexOf(GeneratorConstants.CLASSNAME_VARIABLE)).toEqual(-1);
257-
expect(modifiedContentFive.indexOf(GeneratorConstants.FILENAME_VARIABLE)).toEqual(-1);
258-
expect(modifiedContentFive.indexOf(GeneratorConstants.SUPPLIEDNAME_VARIABLE)).toEqual(-1);
259-
expect(modifiedContentSix.indexOf(GeneratorConstants.CLASSNAME_VARIABLE)).toEqual(-1);
260-
expect(modifiedContentSix.indexOf(GeneratorConstants.FILENAME_VARIABLE)).toEqual(-1);
261-
expect(modifiedContentSix.indexOf(GeneratorConstants.SUPPLIEDNAME_VARIABLE)).toEqual(-1);
252+
const modifiedContentSeven = results.get(fileSeven);
253+
const nonExistentVars = [
254+
GeneratorConstants.CLASSNAME_VARIABLE,
255+
GeneratorConstants.FILENAME_VARIABLE,
256+
GeneratorConstants.SUPPLIEDNAME_VARIABLE,
257+
GeneratorConstants.TAB_CONTENT_VARIABLE,
258+
GeneratorConstants.TAB_VARIABLES_VARIABLE,
259+
];
260+
261+
for (let v of nonExistentVars) {
262+
expect(modifiedContentOne.indexOf(v)).toEqual(-1);
263+
expect(modifiedContentTwo.indexOf(v)).toEqual(-1);
264+
expect(modifiedContentThree.indexOf(v)).toEqual(-1);
265+
expect(modifiedContentFour.indexOf(v)).toEqual(-1);
266+
expect(modifiedContentFive.indexOf(v)).toEqual(-1);
267+
expect(modifiedContentSix.indexOf(v)).toEqual(-1);
268+
expect(modifiedContentSeven.indexOf(v)).toEqual(-1);
269+
}
262270
});
263271
});
264272

@@ -361,4 +369,57 @@ export class $CLASSNAMEModule {}
361369
expect(globAllSpy).toHaveBeenCalledWith(['/path/to/pages/**/*.module.ts', '/path/to/components/**/*.module.ts']);
362370
});
363371
});
372+
373+
describe('tabsModuleManipulation' , () => {
374+
const className = 'SettingsView';
375+
const fileName = 'settings-view';
376+
const suppliedName = 'settings view';
377+
378+
it('should return a succesful promise', () => {
379+
let rejected = false;
380+
381+
util.tabsModuleManipulation([['/src/pages/cool-tab-one/cool-tab-one.module.ts']], { name: suppliedName, className: className, fileName: fileName }, [{ name: suppliedName, className: className, fileName: fileName }]).catch(() => {
382+
rejected = true;
383+
});
384+
385+
expect(rejected).toBeFalsy();
386+
});
387+
388+
it('should throw when files are not written succesfully', () => {
389+
spyOn(helpers, helpers.writeFileAsync.name).and.throwError;
390+
391+
expect(util.tabsModuleManipulation([['/src/pages/cool-tab-one/cool-tab-one.module.ts']], { name: suppliedName, className: className, fileName: fileName }, [{ name: suppliedName, className: className, fileName: fileName }])).toThrow();
392+
});
393+
});
394+
395+
describe('nonPageFileManipulation', () => {
396+
const componentsDir = '/path/to/components';
397+
const directivesDir = '/path/to/directives';
398+
const pagesDir = '/path/to/pages';
399+
const pipesDir = '/path/to/pipes';
400+
const providersDir = '/path/to/providers';
401+
402+
const context = { componentsDir, directivesDir, pagesDir, pipesDir, providersDir };
403+
404+
beforeEach(() => {
405+
const templateDir = '/Users/noone/project/node_modules/ionic-angular/templates';
406+
spyOn(helpers, helpers.getPropertyValue.name).and.returnValue(templateDir);
407+
});
408+
409+
it('should return a succesful promise', () => {
410+
let rejected = false;
411+
412+
util.nonPageFileManipulation(context, 'coolStuff', '/src/pages/cool-tab-one/cool-tab-one.module.ts', 'pipe').catch(() => {
413+
rejected = true;
414+
});
415+
416+
expect(rejected).toBeFalsy();
417+
});
418+
419+
it('should throw when files are not written succesfully', () => {
420+
spyOn(helpers, helpers.writeFileAsync.name).and.throwError;
421+
422+
expect(util.nonPageFileManipulation(context, 'coolStuff', '/src/pages/cool-tab-one/cool-tab-one.module.ts', 'pipe')).toThrow();
423+
});
424+
});
364425
});

src/generators/util.ts

Lines changed: 87 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { basename, dirname, join, relative } from 'path';
22
import { readdirSync } from 'fs';
33
import { Logger} from '../logger/logger';
44

5-
import { paramCase, pascalCase, upperCaseFirst } from 'change-case';
5+
import { camelCase, paramCase, pascalCase, sentenceCase, upperCaseFirst } from 'change-case';
66

77
import * as Constants from '../util/constants';
88
import * as GeneratorConstants from './constants';
@@ -14,8 +14,10 @@ import { appendNgModuleDeclaration, insertNamedImportIfNeeded } from '../util/ty
1414

1515
export function hydrateRequest(context: BuildContext, request: GeneratorRequest) {
1616
const hydrated = Object.assign({ includeNgModule: true }, request) as HydratedGeneratorRequest;
17-
hydrated.className = ensureSuffix(pascalCase(request.name), upperCaseFirst(request.type));
18-
hydrated.fileName = removeSuffix(paramCase(request.name), `-${request.type}`);
17+
const suffix = getSuffixFromGeneratorType(context, request.type);
18+
19+
hydrated.className = ensureSuffix(pascalCase(request.name), upperCaseFirst(suffix));
20+
hydrated.fileName = removeSuffix(paramCase(request.name), `-${paramCase(suffix)}`);
1921

2022
hydrated.dirToRead = join(getPropertyValue(Constants.ENV_VAR_IONIC_ANGULAR_TEMPLATE_DIR), request.type);
2123

@@ -25,6 +27,30 @@ export function hydrateRequest(context: BuildContext, request: GeneratorRequest)
2527
return hydrated;
2628
}
2729

30+
export function hydrateTabRequest(context: BuildContext, request: GeneratorTabRequest) {
31+
const h = hydrateRequest(context, request);
32+
const hydrated = Object.assign({
33+
tabs: request.tabs,
34+
tabContent: '',
35+
tabVariables: ''
36+
}, h) as HydratedGeneratorRequest;
37+
38+
for (let i = 0; i < request.tabs.length; i++) {
39+
const tabVar = `${camelCase(request.tabs[i].name)}Root`;
40+
hydrated.tabVariables += ` ${tabVar} = '${request.tabs[i].className}'\n`;
41+
42+
// If this is the last ion-tab to insert
43+
// then we do not want a new line
44+
if (i === request.tabs.length - 1) {
45+
hydrated.tabContent += ` <ion-tab [root]="${tabVar}" tabTitle="${sentenceCase(request.tabs[i].name)}" tabIcon="information-circle"></ion-tab>`;
46+
} else {
47+
hydrated.tabContent += ` <ion-tab [root]="${tabVar}" tabTitle="${sentenceCase(request.tabs[i].name)}" tabIcon="information-circle"></ion-tab>\n`;
48+
}
49+
}
50+
51+
return hydrated;
52+
}
53+
2854
export function readTemplates(pathToRead: string): Promise<Map<string, string>> {
2955
const fileNames = readdirSync(pathToRead);
3056
const absolutePaths = fileNames.map(fileName => {
@@ -58,10 +84,12 @@ export function filterOutTemplates(request: HydratedGeneratorRequest, templates:
5884
export function applyTemplates(request: HydratedGeneratorRequest, templates: Map<string, string>) {
5985
const appliedTemplateMap = new Map<string, string>();
6086
templates.forEach((fileContent: string, filePath: string) => {
61-
const classnameRemovedContent = replaceAll(fileContent, GeneratorConstants.CLASSNAME_VARIABLE, request.className);
62-
const fileNameRemovedContent = replaceAll(classnameRemovedContent, GeneratorConstants.FILENAME_VARIABLE, request.fileName);
63-
const suppliedNameRemovedContent = replaceAll(fileNameRemovedContent, GeneratorConstants.SUPPLIEDNAME_VARIABLE, request.name);
64-
appliedTemplateMap.set(filePath, suppliedNameRemovedContent);
87+
fileContent = replaceAll(fileContent, GeneratorConstants.CLASSNAME_VARIABLE, request.className);
88+
fileContent = replaceAll(fileContent, GeneratorConstants.FILENAME_VARIABLE, request.fileName);
89+
fileContent = replaceAll(fileContent, GeneratorConstants.SUPPLIEDNAME_VARIABLE, request.name);
90+
fileContent = replaceAll(fileContent, GeneratorConstants.TAB_CONTENT_VARIABLE, request.tabContent);
91+
fileContent = replaceAll(fileContent, GeneratorConstants.TAB_VARIABLES_VARIABLE, request.tabVariables);
92+
appliedTemplateMap.set(filePath, fileContent);
6593
});
6694
return appliedTemplateMap;
6795
}
@@ -94,12 +122,27 @@ export function getNgModules(context: BuildContext, types: string[]): Promise<Gl
94122
return globAll(patterns);
95123
}
96124

125+
function getSuffixFromGeneratorType(context: BuildContext, type: string) {
126+
if (type === Constants.COMPONENT) {
127+
return 'Component';
128+
} else if (type === Constants.DIRECTIVE) {
129+
return 'Directive';
130+
} else if (type === Constants.PAGE || type === Constants.TABS) {
131+
return 'Page';
132+
} else if (type === Constants.PIPE) {
133+
return 'Pipe';
134+
} else if (type === Constants.PROVIDER) {
135+
return 'Provider';
136+
}
137+
throw new Error(`Unknown Generator Type: ${type}`);
138+
}
139+
97140
export function getDirToWriteToByType(context: BuildContext, type: string) {
98141
if (type === Constants.COMPONENT) {
99142
return context.componentsDir;
100143
} else if (type === Constants.DIRECTIVE) {
101144
return context.directivesDir;
102-
} else if (type === Constants.PAGE) {
145+
} else if (type === Constants.PAGE || type === Constants.TABS) {
103146
return context.pagesDir;
104147
} else if (type === Constants.PIPE) {
105148
return context.pipesDir;
@@ -110,30 +153,43 @@ export function getDirToWriteToByType(context: BuildContext, type: string) {
110153
}
111154

112155
export function nonPageFileManipulation(context: BuildContext, name: string, ngModulePath: string, type: string) {
113-
const hydratedRequest = hydrateRequest(context, { type: type, name });
114-
return readFileAsync(ngModulePath).then((fileContent: string) => {
156+
const hydratedRequest = hydrateRequest(context, { type, name });
157+
let fileContent: string;
158+
return readFileAsync(ngModulePath).then((content) => {
159+
fileContent = content;
160+
return generateTemplates(context, hydratedRequest);
161+
}).then(() => {
115162
fileContent = insertNamedImportIfNeeded(ngModulePath, fileContent, hydratedRequest.className, relative(dirname(ngModulePath), hydratedRequest.dirToWrite));
116163
fileContent = appendNgModuleDeclaration(ngModulePath, fileContent, hydratedRequest.className);
117164
return writeFileAsync(ngModulePath, fileContent);
118-
}).then(() => {
119-
return processNonTabRequest(context, hydratedRequest);
120-
}).then(() => {
121-
// TODO
122165
});
123166
}
124167

125-
export function processNonTabRequest(context: BuildContext, request: GeneratorRequest): Promise<string[]> {
126-
Logger.debug('[Generators] processNonTabRequest: Hydrating the request with project data ...');
127-
const hydratedRequest = hydrateRequest(context, request);
128-
Logger.debug('[Generators] processNonTabRequest: Reading templates ...');
129-
return readTemplates(hydratedRequest.dirToRead).then((map: Map<string, string>) => {
130-
Logger.debug('[Generators] processNonTabRequest: Filtering out NgModule and Specs if needed ...');
131-
return filterOutTemplates(hydratedRequest, map);
168+
export function tabsModuleManipulation(tabs: string[][], hydratedRequest: HydratedGeneratorRequest, tabHydratedRequests: HydratedGeneratorRequest[]): Promise<any> {
169+
const ngModulePath = tabs[0].find((element: any): boolean => {
170+
return element.indexOf('module') !== -1;
171+
});
172+
const tabsNgModulePath = `${hydratedRequest.dirToWrite}/${hydratedRequest.fileName}.module.ts`;
173+
174+
return readFileAsync(tabsNgModulePath).then((content) => {
175+
let fileContent = content;
176+
fileContent = insertNamedImportIfNeeded(tabsNgModulePath, fileContent, tabHydratedRequests[0].className, relative(dirname(tabsNgModulePath), ngModulePath.replace('.module.ts', '')));
177+
fileContent = appendNgModuleDeclaration(tabsNgModulePath, fileContent, tabHydratedRequests[0].className);
178+
179+
return writeFileAsync(tabsNgModulePath, fileContent);
180+
});
181+
}
182+
183+
export function generateTemplates(context: BuildContext, request: HydratedGeneratorRequest): Promise<string[]> {
184+
Logger.debug('[Generators] generateTemplates: Reading templates ...');
185+
return readTemplates(request.dirToRead).then((map: Map<string, string>) => {
186+
Logger.debug('[Generators] generateTemplates: Filtering out NgModule and Specs if needed ...');
187+
return filterOutTemplates(request, map);
132188
}).then((filteredMap: Map<string, string>) => {
133-
Logger.debug('[Generators] processNonTabRequest: Applying tempaltes ...');
134-
const appliedTemplateMap = applyTemplates(hydratedRequest, filteredMap);
135-
Logger.debug('[Generators] processNonTabRequest: Writing generated files to disk ...');
136-
return writeGeneratedFiles(hydratedRequest, appliedTemplateMap);
189+
Logger.debug('[Generators] generateTemplates: Applying templates ...');
190+
const appliedTemplateMap = applyTemplates(request, filteredMap);
191+
Logger.debug('[Generators] generateTemplates: Writing generated files to disk ...');
192+
return writeGeneratedFiles(request, appliedTemplateMap);
137193
});
138194
}
139195

@@ -149,9 +205,15 @@ export interface GeneratorRequest {
149205
includeNgModule?: boolean;
150206
};
151207

208+
export interface GeneratorTabRequest extends GeneratorRequest {
209+
tabs?: HydratedGeneratorRequest[];
210+
}
211+
152212
export interface HydratedGeneratorRequest extends GeneratorRequest {
153213
fileName?: string;
154214
className?: string;
215+
tabContent?: string;
216+
tabVariables?: string;
155217
dirToRead?: string;
156218
dirToWrite?: string;
157219
};

src/util/helpers.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,18 @@ describe('helpers', () => {
197197
expect(helpers.removeSuffix('dan dan the sunshine man', ' woman')).toEqual('dan dan the sunshine man');
198198
});
199199
});
200+
201+
describe('replaceAll', () => {
202+
it('should replace a variable', () => {
203+
expect(helpers.replaceAll('hello $VAR world', '$VAR', 'my')).toEqual('hello my world');
204+
});
205+
206+
it('should replace a variable with newlines', () => {
207+
expect(helpers.replaceAll('hello\n $VARMORETEXT\n world', '$VAR', 'NO')).toEqual('hello\n NOMORETEXT\n world');
208+
});
209+
210+
it('should replace a variable and handle undefined', () => {
211+
expect(helpers.replaceAll('hello $VAR world', '$VAR', undefined)).toEqual('hello world');
212+
});
213+
});
200214
});

src/util/helpers.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,10 @@ export function purgeWebpackPrefixFromPath(filePath: string) {
347347
}
348348

349349
export function replaceAll(input: string, toReplace: string, replacement: string) {
350+
if (!replacement) {
351+
replacement = '';
352+
}
353+
350354
return input.split(toReplace).join(replacement);
351355
}
352356

0 commit comments

Comments
 (0)