fix(core): support unit testing of nx generators invoking wrapped schematics
This commit is contained in:
parent
fd18b5edec
commit
3157293752
@ -1,4 +1,6 @@
|
||||
export {
|
||||
wrapAngularDevkitSchematic,
|
||||
overrideCollectionResolutionForTesting,
|
||||
mockSchematicsForTesting,
|
||||
NxScopedHost,
|
||||
} from '@nrwl/tao/src/commands/ngcli-adapter';
|
||||
|
||||
@ -289,13 +289,7 @@ export class NxScopedHost extends virtualFs.ScopedHost<any> {
|
||||
actualConfigFileName: any;
|
||||
isNewFormat: boolean;
|
||||
}> {
|
||||
const p = path.toString();
|
||||
if (
|
||||
p === 'angular.json' ||
|
||||
p === '/angular.json' ||
|
||||
p === 'workspace.json' ||
|
||||
p === '/workspace.json'
|
||||
) {
|
||||
if (isWorkspaceConfigPath(path)) {
|
||||
return super.exists('/angular.json' as any).pipe(
|
||||
switchMap((isAngularJson) => {
|
||||
const actualConfigFileName = isAngularJson
|
||||
@ -340,7 +334,7 @@ export class NxScopedHostForMigrations extends NxScopedHost {
|
||||
}
|
||||
|
||||
read(path: Path): Observable<FileBuffer> {
|
||||
if (this.isWorkspaceConfig(path)) {
|
||||
if (isWorkspaceConfigPath(path)) {
|
||||
return super.read(path).pipe(map(processConfigWhenReading));
|
||||
} else {
|
||||
return super.read(path);
|
||||
@ -348,22 +342,12 @@ export class NxScopedHostForMigrations extends NxScopedHost {
|
||||
}
|
||||
|
||||
write(path: Path, content: FileBuffer) {
|
||||
if (this.isWorkspaceConfig(path)) {
|
||||
if (isWorkspaceConfigPath(path)) {
|
||||
return super.write(path, processConfigWhenWriting(content));
|
||||
} else {
|
||||
return super.write(path, content);
|
||||
}
|
||||
}
|
||||
|
||||
protected isWorkspaceConfig(path: Path) {
|
||||
const p = path.toString();
|
||||
return (
|
||||
p === 'angular.json' ||
|
||||
p === '/angular.json' ||
|
||||
p === 'workspace.json' ||
|
||||
p === '/workspace.json'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class NxScopeHostUsedForWrappedSchematics extends NxScopedHost {
|
||||
@ -372,46 +356,77 @@ export class NxScopeHostUsedForWrappedSchematics extends NxScopedHost {
|
||||
}
|
||||
|
||||
read(path: Path): Observable<FileBuffer> {
|
||||
return this.context(path).pipe(
|
||||
switchMap((r) => {
|
||||
// if it is a workspace config, handle it in a special way
|
||||
if (r.isWorkspaceConfig) {
|
||||
const match = this.host
|
||||
.listChanges()
|
||||
.find(
|
||||
(f) => f.path == 'workspace.json' || f.path == 'angular.json'
|
||||
);
|
||||
if (isWorkspaceConfigPath(path)) {
|
||||
const match = findWorkspaceConfigFileChange(this.host);
|
||||
// no match, default to existing behavior
|
||||
if (!match) {
|
||||
return super.read(path);
|
||||
}
|
||||
|
||||
// no match, default to existing behavior
|
||||
if (!match) {
|
||||
return super.read(path);
|
||||
}
|
||||
|
||||
// we try to format it, if it changes, return it, otherwise return the original change
|
||||
try {
|
||||
const w = JSON.parse(Buffer.from(match.content).toString());
|
||||
const formatted = toOldFormatOrNull(w);
|
||||
return of(
|
||||
formatted
|
||||
? Buffer.from(JSON.stringify(formatted, null, 2))
|
||||
: Buffer.from(match.content)
|
||||
);
|
||||
} catch (e) {
|
||||
return super.read(path);
|
||||
}
|
||||
} else {
|
||||
const targetPath = path.startsWith('/') ? path.substring(1) : path;
|
||||
// found a matching change in the host
|
||||
const match = this.host
|
||||
.listChanges()
|
||||
.find(
|
||||
(f) => f.path == targetPath.toString() && f.type !== 'DELETE'
|
||||
);
|
||||
return match ? of(Buffer.from(match.content)) : super.read(path);
|
||||
}
|
||||
})
|
||||
);
|
||||
// we try to format it, if it changes, return it, otherwise return the original change
|
||||
try {
|
||||
const w = JSON.parse(Buffer.from(match.content).toString());
|
||||
const formatted = toOldFormatOrNull(w);
|
||||
return of(
|
||||
formatted
|
||||
? Buffer.from(JSON.stringify(formatted, null, 2))
|
||||
: Buffer.from(match.content)
|
||||
);
|
||||
} catch (e) {
|
||||
return super.read(path);
|
||||
}
|
||||
} else {
|
||||
// found a matching change in the host
|
||||
const match = findMatchingFileChange(this.host, path);
|
||||
return match ? of(Buffer.from(match.content)) : super.read(path);
|
||||
}
|
||||
}
|
||||
|
||||
exists(path: Path): Observable<boolean> {
|
||||
if (isWorkspaceConfigPath(path)) {
|
||||
return findWorkspaceConfigFileChange(this.host)
|
||||
? of(true)
|
||||
: super.exists(path);
|
||||
} else {
|
||||
return findMatchingFileChange(this.host, path)
|
||||
? of(true)
|
||||
: super.exists(path);
|
||||
}
|
||||
}
|
||||
|
||||
isFile(path: Path): Observable<boolean> {
|
||||
if (isWorkspaceConfigPath(path)) {
|
||||
return findWorkspaceConfigFileChange(this.host)
|
||||
? of(true)
|
||||
: super.isFile(path);
|
||||
} else {
|
||||
return findMatchingFileChange(this.host, path)
|
||||
? of(true)
|
||||
: super.isFile(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findWorkspaceConfigFileChange(host: Tree) {
|
||||
return host
|
||||
.listChanges()
|
||||
.find((f) => f.path == 'workspace.json' || f.path == 'angular.json');
|
||||
}
|
||||
|
||||
function findMatchingFileChange(host: Tree, path: Path) {
|
||||
const targetPath = path.startsWith('/') ? path.substring(1) : path.toString;
|
||||
return host
|
||||
.listChanges()
|
||||
.find((f) => f.path == targetPath.toString() && f.type !== 'DELETE');
|
||||
}
|
||||
|
||||
function isWorkspaceConfigPath(p: Path | string) {
|
||||
return (
|
||||
p === 'angular.json' ||
|
||||
p === '/angular.json' ||
|
||||
p === 'workspace.json' ||
|
||||
p === '/workspace.json'
|
||||
);
|
||||
}
|
||||
|
||||
function processConfigWhenReading(content: ArrayBuffer) {
|
||||
@ -637,11 +652,77 @@ function convertEventTypeToHandleMultipleConfigNames(
|
||||
}
|
||||
}
|
||||
|
||||
let collectionResolutionOverrides = null;
|
||||
let mockedSchematics = null;
|
||||
|
||||
/**
|
||||
* By default, Angular Devkit schematic collections will be resolved using the Node resolution.
|
||||
* This doesn't work if you are testing schematics that refer to other schematics in the
|
||||
* same repo.
|
||||
*
|
||||
* This function can can be used to override the resolution behaviour.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* overrideCollectionResolutionForTesting({
|
||||
* '@nrwl/workspace': path.join(__dirname, '../../../../workspace/collection.json'),
|
||||
* '@nrwl/angular': path.join(__dirname, '../../../../angular/collection.json'),
|
||||
* '@nrwl/linter': path.join(__dirname, '../../../../linter/collection.json')
|
||||
* });
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
export function overrideCollectionResolutionForTesting(collections: {
|
||||
[name: string]: string;
|
||||
}) {
|
||||
collectionResolutionOverrides = collections;
|
||||
}
|
||||
|
||||
/**
|
||||
* If you have an Nx Devkit generator invoking the wrapped Angular Devkit schematic,
|
||||
* and you don't want the Angular Devkit schematic to run, you can mock it up using this function.
|
||||
*
|
||||
* Unfortunately, there are some edge cases in the Nx-Angular devkit integration that
|
||||
* can be seen in the unit tests context. This function is useful for handling that as well.
|
||||
*
|
||||
* In this case, you can mock it up.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* mockSchematicsForTesting({
|
||||
* 'mycollection:myschematic': (tree, params) => {
|
||||
* tree.write('README.md');
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
export function mockSchematicsForTesting(schematics: {
|
||||
[name: string]: (
|
||||
host: Tree,
|
||||
generatorOptions: { [k: string]: any }
|
||||
) => Promise<void>;
|
||||
}) {
|
||||
mockedSchematics = schematics;
|
||||
}
|
||||
|
||||
export function wrapAngularDevkitSchematic(
|
||||
collectionName: string,
|
||||
generatorName: string
|
||||
) {
|
||||
return async (host: Tree, generatorOptions: { [k: string]: any }) => {
|
||||
if (
|
||||
mockedSchematics &&
|
||||
mockedSchematics[`${collectionName}:${generatorName}`]
|
||||
) {
|
||||
return await mockedSchematics[`${collectionName}:${generatorName}`](
|
||||
host,
|
||||
generatorOptions
|
||||
);
|
||||
}
|
||||
|
||||
const emptyLogger = {
|
||||
log: (e) => {},
|
||||
info: (e) => {},
|
||||
@ -694,6 +775,19 @@ export function wrapAngularDevkitSchematic(
|
||||
defaults: false,
|
||||
};
|
||||
const workflow = createWorkflow(fsHost, host.root, options);
|
||||
|
||||
// used for testing
|
||||
if (collectionResolutionOverrides) {
|
||||
const r = workflow.engineHost.resolve;
|
||||
workflow.engineHost.resolve = (collection, b, c) => {
|
||||
if (collectionResolutionOverrides[collection]) {
|
||||
return collectionResolutionOverrides[collection];
|
||||
} else {
|
||||
return r.apply(workflow.engineHost, [collection, b, c]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const collection = getCollection(workflow, collectionName);
|
||||
const schematic = collection.createSchematic(generatorName, true);
|
||||
const res = await runSchematic(
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`preset should create files (preset = angular) 1`] = `
|
||||
Array [
|
||||
"tsconfig.editor.json",
|
||||
"tsconfig.json",
|
||||
"src",
|
||||
".browserslistrc",
|
||||
"tsconfig.app.json",
|
||||
".eslintrc.json",
|
||||
"jest.config.js",
|
||||
"tsconfig.spec.json",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`preset should create files (preset = angular) 2`] = `
|
||||
Array [
|
||||
"favicon.ico",
|
||||
"index.html",
|
||||
"main.ts",
|
||||
"polyfills.ts",
|
||||
"styles.css",
|
||||
"assets",
|
||||
"environments",
|
||||
"app",
|
||||
"test-setup.ts",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`preset should create files (preset = angular) 3`] = `
|
||||
Array [
|
||||
"app.module.ts",
|
||||
"app.component.css",
|
||||
"app.component.html",
|
||||
"app.component.spec.ts",
|
||||
"app.component.ts",
|
||||
]
|
||||
`;
|
||||
@ -1,131 +1,132 @@
|
||||
import { readJson, Tree } from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { overrideCollectionResolutionForTesting } from '@nrwl/devkit/ngcli-adapter';
|
||||
import { presetGenerator } from './preset';
|
||||
import * as path from 'path';
|
||||
|
||||
describe('preset', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
});
|
||||
|
||||
describe('--preset', () => {
|
||||
// TODO: reenable. This doesn't work because wrapAngularDevkit uses the fs
|
||||
xdescribe('angular', () => {
|
||||
it('should create files (preset = angular)', async () => {
|
||||
await presetGenerator(tree, {
|
||||
name: 'proj',
|
||||
preset: 'angular',
|
||||
cli: 'nx',
|
||||
});
|
||||
expect(tree.children('apps/proj')).toMatchSnapshot();
|
||||
expect(tree.children('apps/proj/src/')).toMatchSnapshot();
|
||||
expect(tree.children('apps/proj/src/app')).toMatchSnapshot();
|
||||
console.log(tree.children(''));
|
||||
|
||||
expect(
|
||||
JSON.parse(tree.read('/workspace.json').toString()).cli
|
||||
.defaultCollection
|
||||
).toBe('@nrwl/angular');
|
||||
});
|
||||
});
|
||||
|
||||
describe('web-components', () => {
|
||||
it('should create files (preset = web-components)', async () => {
|
||||
await presetGenerator(tree, {
|
||||
name: 'proj',
|
||||
preset: 'web-components',
|
||||
cli: 'nx',
|
||||
});
|
||||
expect(tree.exists('/apps/proj/src/main.ts')).toBe(true);
|
||||
expect(readJson(tree, '/workspace.json').cli.defaultCollection).toBe(
|
||||
'@nrwl/web'
|
||||
);
|
||||
});
|
||||
overrideCollectionResolutionForTesting({
|
||||
'@nrwl/workspace': path.join(
|
||||
__dirname,
|
||||
'../../../../workspace/collection.json'
|
||||
),
|
||||
'@nrwl/angular': path.join(
|
||||
__dirname,
|
||||
'../../../../angular/collection.json'
|
||||
),
|
||||
'@nrwl/linter': path.join(
|
||||
__dirname,
|
||||
'../../../../linter/collection.json'
|
||||
),
|
||||
'@nrwl/nest': path.join(__dirname, '../../../../nest/collection.json'),
|
||||
'@nrwl/node': path.join(__dirname, '../../../../node/collection.json'),
|
||||
'@nrwl/jest': path.join(__dirname, '../../../../jest/collection.json'),
|
||||
'@nrwl/cypress': path.join(
|
||||
__dirname,
|
||||
'../../../../cypress/collection.json'
|
||||
),
|
||||
'@nrwl/express': path.join(
|
||||
__dirname,
|
||||
'../../../../express/collection.json'
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
// it('should create files (preset = react)', async () => {
|
||||
// const tree = await runSchematic(
|
||||
// 'preset',
|
||||
// { name: 'proj', preset: 'react' },
|
||||
// tree
|
||||
// );
|
||||
// expect(tree.exists('/apps/proj/src/main.tsx')).toBe(true);
|
||||
// expect(
|
||||
// JSON.parse(tree.readContent('/workspace.json')).cli.defaultCollection
|
||||
// ).toBe('@nrwl/react');
|
||||
// });
|
||||
//
|
||||
//
|
||||
// it('should create files (preset = next)', async () => {
|
||||
// const tree = await runSchematic(
|
||||
// 'preset',
|
||||
// { name: 'proj', preset: 'next' },
|
||||
// tree
|
||||
// );
|
||||
// expect(tree.exists('/apps/proj/pages/index.tsx')).toBe(true);
|
||||
// expect(
|
||||
// JSON.parse(tree.readContent('/workspace.json')).cli.defaultCollection
|
||||
// ).toBe('@nrwl/next');
|
||||
// });
|
||||
//
|
||||
// describe('--preset angular-nest', () => {
|
||||
// it('should create files', async () => {
|
||||
// const tree = await runSchematic(
|
||||
// 'preset',
|
||||
// { name: 'proj', preset: 'angular-nest' },
|
||||
// tree
|
||||
// );
|
||||
// expect(tree.exists('/apps/proj/src/app/app.component.ts')).toBe(true);
|
||||
// expect(tree.exists('/apps/api/src/app/app.controller.ts')).toBe(true);
|
||||
// expect(
|
||||
// tree.exists('/libs/api-interfaces/src/lib/api-interfaces.ts')
|
||||
// ).toBe(true);
|
||||
// });
|
||||
//
|
||||
// it('should work with unnormalized names', async () => {
|
||||
// const tree = await runSchematic(
|
||||
// 'preset',
|
||||
// { name: 'myProj', preset: 'angular-nest' },
|
||||
// tree
|
||||
// );
|
||||
//
|
||||
// expect(tree.exists('/apps/my-proj/src/app/app.component.ts')).toBe(true);
|
||||
// expect(tree.exists('/apps/api/src/app/app.controller.ts')).toBe(true);
|
||||
// expect(
|
||||
// tree.exists('/libs/api-interfaces/src/lib/api-interfaces.ts')
|
||||
// ).toBe(true);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe('--preset react-express', () => {
|
||||
// it('should create files', async () => {
|
||||
// const tree = await runSchematic(
|
||||
// 'preset',
|
||||
// { name: 'proj', preset: 'react-express' },
|
||||
// tree
|
||||
// );
|
||||
// expect(tree.exists('/apps/proj/src/app/app.tsx')).toBe(true);
|
||||
// expect(
|
||||
// tree.exists('/libs/api-interfaces/src/lib/api-interfaces.ts')
|
||||
// ).toBe(true);
|
||||
// expect(tree.exists('/apps/proj/.eslintrc.json')).toBe(true);
|
||||
// expect(tree.exists('/apps/api/.eslintrc.json')).toBe(true);
|
||||
// expect(tree.exists('/libs/api-interfaces/.eslintrc.json')).toBe(true);
|
||||
// });
|
||||
//
|
||||
// it('should work with unnormalized names', async () => {
|
||||
// const tree = await runSchematic(
|
||||
// 'preset',
|
||||
// { name: 'myProj', preset: 'react-express' },
|
||||
// tree
|
||||
// );
|
||||
//
|
||||
// expect(tree.exists('/apps/my-proj/src/app/app.tsx')).toBe(true);
|
||||
// expect(
|
||||
// tree.exists('/libs/api-interfaces/src/lib/api-interfaces.ts')
|
||||
// ).toBe(true);
|
||||
// });
|
||||
// });
|
||||
afterEach(() => {
|
||||
overrideCollectionResolutionForTesting(null);
|
||||
});
|
||||
|
||||
it('should create files (preset = angular)', async () => {
|
||||
await presetGenerator(tree, {
|
||||
name: 'proj',
|
||||
preset: 'angular',
|
||||
cli: 'nx',
|
||||
});
|
||||
expect(tree.children('apps/proj')).toMatchSnapshot();
|
||||
expect(tree.children('apps/proj/src/')).toMatchSnapshot();
|
||||
expect(tree.children('apps/proj/src/app')).toMatchSnapshot();
|
||||
console.log(tree.children(''));
|
||||
|
||||
expect(
|
||||
JSON.parse(tree.read('/workspace.json').toString()).cli.defaultCollection
|
||||
).toBe('@nrwl/angular');
|
||||
});
|
||||
|
||||
it('should create files (preset = web-components)', async () => {
|
||||
await presetGenerator(tree, {
|
||||
name: 'proj',
|
||||
preset: 'web-components',
|
||||
cli: 'nx',
|
||||
});
|
||||
expect(tree.exists('/apps/proj/src/main.ts')).toBe(true);
|
||||
expect(readJson(tree, '/workspace.json').cli.defaultCollection).toBe(
|
||||
'@nrwl/web'
|
||||
);
|
||||
});
|
||||
|
||||
it('should create files (preset = react)', async () => {
|
||||
await presetGenerator(tree, {
|
||||
name: 'proj',
|
||||
preset: 'react',
|
||||
style: 'css',
|
||||
linter: 'eslint',
|
||||
cli: 'nx',
|
||||
});
|
||||
expect(tree.exists('/apps/proj/src/main.tsx')).toBe(true);
|
||||
expect(readJson(tree, '/workspace.json').cli.defaultCollection).toBe(
|
||||
'@nrwl/react'
|
||||
);
|
||||
});
|
||||
|
||||
it('should create files (preset = next)', async () => {
|
||||
await presetGenerator(tree, {
|
||||
name: 'proj',
|
||||
preset: 'next',
|
||||
style: 'css',
|
||||
linter: 'eslint',
|
||||
cli: 'nx',
|
||||
});
|
||||
expect(tree.exists('/apps/proj/pages/index.tsx')).toBe(true);
|
||||
expect(readJson(tree, '/workspace.json').cli.defaultCollection).toBe(
|
||||
'@nrwl/next'
|
||||
);
|
||||
});
|
||||
|
||||
it('should create files (preset = angular-nest)', async () => {
|
||||
await presetGenerator(tree, {
|
||||
name: 'proj',
|
||||
preset: 'angular-nest',
|
||||
style: 'css',
|
||||
linter: 'eslint',
|
||||
cli: 'nx',
|
||||
});
|
||||
|
||||
expect(tree.exists('/apps/proj/src/app/app.component.ts')).toBe(true);
|
||||
expect(tree.exists('/apps/api/src/app/app.controller.ts')).toBe(true);
|
||||
expect(tree.exists('/libs/api-interfaces/src/lib/api-interfaces.ts')).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('should create files (preset react-express)', async () => {
|
||||
await presetGenerator(tree, {
|
||||
name: 'proj',
|
||||
preset: 'react-express',
|
||||
style: 'css',
|
||||
linter: 'eslint',
|
||||
cli: 'nx',
|
||||
});
|
||||
|
||||
expect(tree.exists('/apps/proj/src/app/app.tsx')).toBe(true);
|
||||
expect(tree.exists('/libs/api-interfaces/src/lib/api-interfaces.ts')).toBe(
|
||||
true
|
||||
);
|
||||
expect(tree.exists('/apps/proj/.eslintrc.json')).toBe(true);
|
||||
expect(tree.exists('/apps/api/.eslintrc.json')).toBe(true);
|
||||
expect(tree.exists('/libs/api-interfaces/.eslintrc.json')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@ -20,6 +20,8 @@ function createHost(tree: Tree): workspaces.WorkspaceHost {
|
||||
},
|
||||
async isDirectory(path: string): Promise<boolean> {
|
||||
// approximate a directory check
|
||||
// special case needed when testing wrapped schematics
|
||||
if (path === '/') return true;
|
||||
return !tree.exists(path) && tree.getDir(path).subfiles.length > 0;
|
||||
},
|
||||
async isFile(path: string): Promise<boolean> {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user