From 0c895e9ce5ccef25c35fefbfbfbe00cce4627f0b Mon Sep 17 00:00:00 2001 From: Denis Zholob Date: Thu, 8 Jun 2023 11:36:26 -0600 Subject: [PATCH] Change test templates to be without TestBed * Add option to use test bed templates for specs * By default use templates with no test bed for better performance and focus on unit testing functions * Add a more generic arrayUnionOverride function to use in generator. * Update readme and contributing * Update package version and changelog --- .vscode/settings.json | 3 + CHANGELOG.md | 16 +++- CONTRIBUTING.md | 16 ++++ README.md | 15 ++-- package.json | 9 ++- src/generator/array-functions.ts | 21 ++++++ src/generator/generate-files.ts | 73 +++++++++---------- src/generator/settings.ts | 12 +-- .../__name__.component.spec.ts.mustache | 25 +++++++ .../__name__.directive.spec.ts.mustache | 21 ++++++ .../__name__.module.spec.ts.mustache | 16 ++++ .../__name__.service.spec.ts.mustache | 16 ++++ .../__name__.component.spec.ts.mustache | 16 +--- .../__name__.directive.spec.ts.mustache | 10 --- .../__name__.module.spec.ts.mustache | 11 +-- .../__name__.service.spec.ts.mustache | 9 +-- 16 files changed, 194 insertions(+), 95 deletions(-) create mode 100644 src/templates-alt/__name__.component.spec.ts.mustache create mode 100644 src/templates-alt/__name__.directive.spec.ts.mustache create mode 100644 src/templates-alt/__name__.module.spec.ts.mustache create mode 100644 src/templates-alt/__name__.service.spec.ts.mustache diff --git a/.vscode/settings.json b/.vscode/settings.json index 22d7d57..0ee12d4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,9 @@ "explorer.autoRevealExclude": { "**/node_modules": true }, + "material-icon-theme.folders.associations": { + "templates-alt": "template" + }, "files.exclude": { ".husky": true, ".vscode-test": true, diff --git a/CHANGELOG.md b/CHANGELOG.md index 71b20c4..be30003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,16 +2,24 @@ ## [Unreleased] +## [0.0.7] +### Added +- New setting to toggle `TestBed` in angular spec tests +### Changed +- Default specs do not have the `TestBed` +- Can toggle `defaultSpecsUseTestBed` setting to revert back to using `TestBed` + + ## [0.0.6] ### Added - - Error message popup for any errors that were encountered +- Error message popup for any errors that were encountered ### Changed - - If there are extra custom templates that has the known file suffixes (component, service, pipe, etc..), they will be rendered as well. +- If there are extra custom templates that has the known file suffixes (component, service, pipe, etc..), they will be rendered as well. ## [0.0.5] ### Added - - More generator options: Route, Directive, Pipe - - New setting options to toggle specs and stories +- More generator options: Route, Directive, Pipe +- New setting options to toggle specs and stories ### Changed - Template variable names to be more consistent diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 32f3158..34b1fdc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,19 @@ +## Dev +* Run `npm i` to install dev npm packages +* Go to the debugger window (Run&Debug icon on side bar) +* Click `Run Extension` + * New window will open where you can test the extension code changes + * A watch task will be automatically spawned to rebuild the extension as code changes + * NOTE: Sometimes the watch task does not pick up the template changes, so you need to run the `npm run template-copy` command + +## Commit/Push/Publish +* Commit as much as you like, but squash changes into 1 commit before a new release push +* Make sure to update the following before pushing/publishing + * [package.json](./package.json) version number + * [CHANGELOG](./CHANGELOG.md) version change description +* Tag the commit to be the published release and push +* [CI/CD](.github/workflows/main.yml) will automatically build and publish the extension from main branch + ## References * [Extension Development Quick Start](./vsc-extension-quickstart.md) * [VsCode First Extension Doc](https://code.visualstudio.com/api/get-started/your-first-extension) diff --git a/README.md b/README.md index 82322b2..44517dd 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,13 @@ If you find the extension or the source code useful, consider: In `.vscode/settings.json` add in the following settings to customize the extension. * `customTemplateFolder` - Custom template folder location. Path relative you your workspace root. If null will use extension default templates. +* `defaultSpecsUseTestBed` - Toggle using default spec templates with angular TestBed or without it for better performance. * `generateSpec` - Toggle generation of spec files. * `generateStories` - Toggle generation of stories files. ```json "angular-files-generator.customTemplateFolder": ".vscode/ngfg-templates", + "angular-files-generator.defaultSpecsUseTestBed": false, "angular-files-generator.generateSpec": true, "angular-files-generator.generateStories": true, ``` @@ -54,14 +56,15 @@ In `.vscode/settings.json` add in the following settings to customize the extens ## Requirements -Generator creates the files from custom templates, so nothing is needed to create the files. -To use the files install +Generator creates the files from custom templates, so **nothing is needed to create** the files! +However, to use the files install -- [Angular](https://angular.io/docs) -- [Jest](https://jestjs.io/docs/testing-frameworks) -- [Storybook](https://storybook.js.org/docs/angular/get-started/introduction) +- [Angular](https://angular.io/docs) for component code +- [Jest](https://jestjs.io/docs/testing-frameworks) for unit testing +- [Storybook](https://storybook.js.org/docs/angular/get-started/introduction) for visual tests/documentation +- [ng-mocks](https://ng-mocks.sudo.eu/) recommended for helping mock services, etc.. for jest or storybook -## Known Issues +## Known Issues/Feature Requests https://github.com/deniszholob/angular-files-generator/issues diff --git a/package.json b/package.json index 92b36f3..d2d8a6c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/deniszholob/angular-files-generator/issues" }, "icon": "images/icon-96x96.png", - "version": "0.0.6", + "version": "0.0.7", "license": "GPL-3", "engines": { "node": ">=18.12.1", @@ -48,6 +48,11 @@ "default": null, "description": "Custom template folder location. Path relative you your workspace root. If null will use extension default templates." }, + "angular-files-generator.defaultSpecsUseTestBed": { + "type": "boolean", + "default": false, + "description": "Toggle using default spec templates with angular TestBed or without it for better performance." + }, "angular-files-generator.generateSpec": { "type": "boolean", "default": true, @@ -146,7 +151,7 @@ "package": "npx vsce package", "publish": "npx vsce publish", "remove-output": "rimraf ./out ", - "template-copy": "copyfiles -f ./src/templates/**/*.mustache ./out/templates/", + "template-copy": "copyfiles -f ./src/templates/**/*.mustache ./out/templates/ && copyfiles -f ./src/templates-alt/**/*.mustache ./out/templates-alt/", "compile": "npm run pre-build && tsc -p ./", "watch": "npm run pre-build && tsc -watch -p ./", "pretest": "npm run compile && npm run lint", diff --git a/src/generator/array-functions.ts b/src/generator/array-functions.ts index e884e4b..6c78359 100644 --- a/src/generator/array-functions.ts +++ b/src/generator/array-functions.ts @@ -34,3 +34,24 @@ export function arrayDifference( ): T[] { return arrA.filter((x) => !arrayIncludes(arrB, x, comparator)); } + +/** + * If arrA=[{n="bob",a="5"},{n="alice",a="10"}] + * And if arrB=[{n="alice",a="11"},{n="jack",a="20"}] + * And if comparator=(a,b)=>a.n===b.n + * Then return arr=[{n="bob",a="5"},{n="alice",a="11"},{n="jack",a="20"}] + * @return Elements in both arrays, with similar elements in B overriding elements in A + */ +export function arrayUnionOverride( + arrA: T[], + arrB: T[], + comparator: ArrayComparator +): T[] { + // A contains items not in B + const ADiffB: T[] = arrayDifference(arrA, arrB, comparator); + // B contains overrides for A + const BIntersA: T[] = arrayIntersection(arrB, arrA, comparator); + // B contains extra items not in A + const BDiffA: T[] = arrayDifference(arrB, arrA, comparator); + return [...ADiffB, ...BIntersA, ...BDiffA]; +} diff --git a/src/generator/generate-files.ts b/src/generator/generate-files.ts index f8421ef..2c092d0 100644 --- a/src/generator/generate-files.ts +++ b/src/generator/generate-files.ts @@ -9,6 +9,7 @@ import { NgFileType } from './angular-file-type.model'; import { log } from './formatter'; import { getSetting_customTemplateFolder, + getSetting_defaultSpecsUseTestBed, getSetting_generateSpec, getSetting_generateStories, } from './settings'; @@ -16,9 +17,11 @@ import { ArrayComparator, arrayDifference, arrayIntersection, + arrayUnionOverride, } from './array-functions'; export const TEMPLATES_FOLDER = 'templates'; +export const TEMPLATES_ALT_FOLDER = 'templates-alt'; // Currently are alt tests using TestBed export interface GeneratorVariables { /** Ex: c:/angular-files-generator/out */ extensionSrcDir: string; @@ -40,6 +43,9 @@ interface Templates { templateFiles: string[]; } +const comparatorTemplateFile: ArrayComparator = (a, b) => + a.name === b.name; + /** Main generator function: Gathers template files and converts to angular files via Mustache.js */ export async function generate( templateVariables: TemplateVariables, @@ -69,18 +75,24 @@ export async function generate( templateVariables ); } +/** @returns templates from directory */ +async function getTemplates(templatesPath: string): Promise { + const templateFiles: string[] = await fs.promises.readdir(templatesPath); + return { templatesPath, templateFiles }; + // return templateFiles.map((f) => new TemplateFile(f, templatesPath)); +} /** @returns extension's default templates */ async function getExtensionTemplates( - extensionSrcDir: string + extensionSrcDir: string, + templateFolder: + | typeof TEMPLATES_FOLDER + | typeof TEMPLATES_ALT_FOLDER = TEMPLATES_FOLDER ): Promise { log('extensionSrcDir:', extensionSrcDir); - const templatesPath: string = path.join(extensionSrcDir, TEMPLATES_FOLDER); + const templatesPath: string = path.join(extensionSrcDir, templateFolder); log('templatesPath:', templatesPath); - const templateFiles: string[] = await fs.promises.readdir(templatesPath); - return { templateFiles, templatesPath }; - - // return templateFiles.map((f) => new TemplateFile(f, templatesPath)); + return getTemplates(templatesPath); } /** @returns user's custom templates */ @@ -94,10 +106,9 @@ async function getCustomTemplates(): Promise { ? path.join(workspaceRoot, customTemplatesFolderName) : undefined; - if (!(templatesPath && fs.existsSync(templatesPath))) return undefined; - - const templateFiles: string[] = await fs.promises.readdir(templatesPath); - return { templateFiles, templatesPath }; + return templatesPath && fs.existsSync(templatesPath) + ? getTemplates(templatesPath) + : undefined; } /** @@ -130,44 +141,32 @@ function templatesToTemplateFiles(templates: Templates): TemplateFile[] { async function getRenderTemplates( extensionSrcDir: string ): Promise { - const defaultTemplates: Templates = await getExtensionTemplates( - extensionSrcDir + let defaultTemplateFiles: TemplateFile[] = templatesToTemplateFiles( + await getExtensionTemplates(extensionSrcDir, TEMPLATES_FOLDER) ); - const customTemplates: Templates | undefined = await getCustomTemplates(); - const defaultTemplateFiles: TemplateFile[] = - templatesToTemplateFiles(defaultTemplates); + if (getSetting_defaultSpecsUseTestBed()) { + defaultTemplateFiles = arrayUnionOverride( + defaultTemplateFiles, + templatesToTemplateFiles( + await getExtensionTemplates(extensionSrcDir, TEMPLATES_ALT_FOLDER) + ), + comparatorTemplateFile + ); + } + + const customTemplates: Templates | undefined = await getCustomTemplates(); if (!customTemplates) return defaultTemplateFiles; // Override and extra template Logic const customTemplateFiles: TemplateFile[] = templatesToTemplateFiles(customTemplates); - const comparator: ArrayComparator = (a, b) => a.name === b.name; - // Default Templates that do not have a corresponding user override - const nonOverridableDefaultTemplateFiles: TemplateFile[] = arrayDifference( + return arrayUnionOverride( defaultTemplateFiles, customTemplateFiles, - comparator + comparatorTemplateFile ); - // Templates that need to be overridden - const overrideTemplateFiles: TemplateFile[] = arrayIntersection( - customTemplateFiles, - defaultTemplateFiles, - comparator - ); - // Custom Templates that do not have a corresponding default template - const extraCustomTemplateFiles: TemplateFile[] = arrayDifference( - customTemplateFiles, - defaultTemplateFiles, - comparator - ); - - return [ - ...nonOverridableDefaultTemplateFiles, - ...overrideTemplateFiles, - ...extraCustomTemplateFiles, - ]; } /** @returns Filtered array based on the user selected generator option (component, module service or both component+module) */ diff --git a/src/generator/settings.ts b/src/generator/settings.ts index c72f8a8..dc97b77 100644 --- a/src/generator/settings.ts +++ b/src/generator/settings.ts @@ -1,20 +1,22 @@ // Settings // From package.json/contributes/configuration/properties import * as vscode from 'vscode'; - export const getSetting_customTemplateFolder = () => getExtensionSetting('customTemplateFolder'); +export const getSetting_defaultSpecsUseTestBed = () => + getExtensionSetting('defaultSpecsUseTestBed'); + export const getSetting_generateSpec = () => getExtensionSetting('generateSpec'); export const getSetting_generateStories = () => getExtensionSetting('generateStories'); -function getExtensionSettings(): vscode.WorkspaceConfiguration { - return vscode.workspace.getConfiguration('angular-files-generator'); -} - function getExtensionSetting(settingKey: string): T | undefined { return getExtensionSettings().get(settingKey); } + +function getExtensionSettings(): vscode.WorkspaceConfiguration { + return vscode.workspace.getConfiguration('angular-files-generator'); +} diff --git a/src/templates-alt/__name__.component.spec.ts.mustache b/src/templates-alt/__name__.component.spec.ts.mustache new file mode 100644 index 0000000..11e4417 --- /dev/null +++ b/src/templates-alt/__name__.component.spec.ts.mustache @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { {{upperCamelCaseName}}Component } from './{{dashCaseName}}.component'; +import { {{upperCamelCaseName}}Module } from './{{dashCaseName}}.module'; + +describe('{{upperCamelCaseName}}Component', () => { + let component: {{upperCamelCaseName}}Component; + let fixture: ComponentFixture<{{upperCamelCaseName}}Component>; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [{{upperCamelCaseName}}Module], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent({{upperCamelCaseName}}Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/templates-alt/__name__.directive.spec.ts.mustache b/src/templates-alt/__name__.directive.spec.ts.mustache new file mode 100644 index 0000000..52587b3 --- /dev/null +++ b/src/templates-alt/__name__.directive.spec.ts.mustache @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { {{upperCamelCaseName}}Directive } from './{{dashCaseName}}.directive'; + +describe('{{upperCamelCaseName}}Directive', () => { + let directive: {{upperCamelCaseName}}Directive; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + providers: [{{upperCamelCaseName}}Directive], + }).compileComponents(); + })); + + beforeEach(() => { + directive = TestBed.inject({{upperCamelCaseName}}Directive); + }); + + it('should create', () => { + expect(directive).toBeTruthy(); + }); +}); diff --git a/src/templates-alt/__name__.module.spec.ts.mustache b/src/templates-alt/__name__.module.spec.ts.mustache new file mode 100644 index 0000000..81bb604 --- /dev/null +++ b/src/templates-alt/__name__.module.spec.ts.mustache @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { {{upperCamelCaseName}}Module } from './{{dashCaseName}}.module'; + +describe('{{upperCamelCaseName}}Module', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [{{upperCamelCaseName}}Module], + }); + }); + + it('should create', () => { + const module: {{upperCamelCaseName}}Module = TestBed.inject({{upperCamelCaseName}}Module); + expect(module).toBeTruthy(); + }); +}); diff --git a/src/templates-alt/__name__.service.spec.ts.mustache b/src/templates-alt/__name__.service.spec.ts.mustache new file mode 100644 index 0000000..c58b66f --- /dev/null +++ b/src/templates-alt/__name__.service.spec.ts.mustache @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { {{upperCamelCaseName}}Service } from './{{dashCaseName}}.service'; + +describe('{{upperCamelCaseName}}Service', () => { + let service: {{upperCamelCaseName}}Service; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject({{upperCamelCaseName}}Service); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/templates/__name__.component.spec.ts.mustache b/src/templates/__name__.component.spec.ts.mustache index 11e4417..38fc3ec 100644 --- a/src/templates/__name__.component.spec.ts.mustache +++ b/src/templates/__name__.component.spec.ts.mustache @@ -1,22 +1,10 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; - import { {{upperCamelCaseName}}Component } from './{{dashCaseName}}.component'; -import { {{upperCamelCaseName}}Module } from './{{dashCaseName}}.module'; describe('{{upperCamelCaseName}}Component', () => { - let component: {{upperCamelCaseName}}Component; - let fixture: ComponentFixture<{{upperCamelCaseName}}Component>; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [{{upperCamelCaseName}}Module], - }).compileComponents(); - })); + let component: {{upperCamelCaseName}}Component = new {{upperCamelCaseName}}Component(); beforeEach(() => { - fixture = TestBed.createComponent({{upperCamelCaseName}}Component); - component = fixture.componentInstance; - fixture.detectChanges(); + component = new {{upperCamelCaseName}}Component(); }); it('should create', () => { diff --git a/src/templates/__name__.directive.spec.ts.mustache b/src/templates/__name__.directive.spec.ts.mustache index 2b4b361..1c54052 100644 --- a/src/templates/__name__.directive.spec.ts.mustache +++ b/src/templates/__name__.directive.spec.ts.mustache @@ -3,16 +3,6 @@ import { {{upperCamelCaseName}}Directive } from './{{dashCaseName}}.directive'; describe('{{upperCamelCaseName}}Directive', () => { const directive: {{upperCamelCaseName}}Directive = new {{upperCamelCaseName}}Directive(); - // beforeEach(waitForAsync(() => { - // TestBed.configureTestingModule({ - // providers: [NewDirectiveDirective], - // }).compileComponents(); - // })); - - // beforeEach(() => { - // directive = TestBed.inject(NewDirectiveDirective); - // }); - it('should create', () => { expect(directive).toBeTruthy(); }); diff --git a/src/templates/__name__.module.spec.ts.mustache b/src/templates/__name__.module.spec.ts.mustache index eb21f8a..665744f 100644 --- a/src/templates/__name__.module.spec.ts.mustache +++ b/src/templates/__name__.module.spec.ts.mustache @@ -1,16 +1,9 @@ -import { TestBed } from '@angular/core/testing'; - import { {{upperCamelCaseName}}Module } from './{{dashCaseName}}.module'; describe('{{upperCamelCaseName}}Module', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [{{upperCamelCaseName}}Module], - }); - }); + const module: {{upperCamelCaseName}}Module = new {{upperCamelCaseName}}Module(); - it('initializes', () => { - const module: {{upperCamelCaseName}}Module = TestBed.inject({{upperCamelCaseName}}Module); + it('should create', () => { expect(module).toBeTruthy(); }); }); diff --git a/src/templates/__name__.service.spec.ts.mustache b/src/templates/__name__.service.spec.ts.mustache index c58b66f..ff5f49d 100644 --- a/src/templates/__name__.service.spec.ts.mustache +++ b/src/templates/__name__.service.spec.ts.mustache @@ -1,14 +1,7 @@ -import { TestBed } from '@angular/core/testing'; - import { {{upperCamelCaseName}}Service } from './{{dashCaseName}}.service'; describe('{{upperCamelCaseName}}Service', () => { - let service: {{upperCamelCaseName}}Service; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject({{upperCamelCaseName}}Service); - }); + const service: {{upperCamelCaseName}}Service = new {{upperCamelCaseName}}Service(); it('should create', () => { expect(service).toBeTruthy();