nx/packages/linter/src/utils/convert-tslint-to-eslint/project-converter.spec.ts
2023-04-25 17:57:36 -04:00

418 lines
13 KiB
TypeScript

import {
addDependenciesToPackageJson,
addProjectConfiguration,
NxJsonConfiguration,
readJson,
readProjectConfiguration,
Tree,
writeJson,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { ProjectConverter } from './project-converter';
/**
* Don't run actual child_process implementation of installPackagesTask()
*/
jest.mock('child_process', () => {
return {
...jest.requireActual<any>('child_process'),
execSync: jest.fn((command: string) => {
if (command.includes('pnpm --version')) {
return '8.2.0';
}
return;
}),
};
});
/**
* Don't run the conversion util, it touches the file system and has its own tests
*/
jest.mock('./utils', () => {
return {
// Since upgrading to (ts-)jest 26 this usage of this mock has caused issues...
// @ts-ignore
...jest.requireActual<any>('./utils'),
convertTSLintConfig: jest.fn(() => {
return {
convertedESLintConfig: {
rules: {
'some-converted-rule': 'error',
},
},
unconvertedTSLintRules: [],
ensureESLintPlugins: [],
};
}),
};
});
describe('ProjectConverter', () => {
let host: Tree;
const projectName = 'foo';
const projectRoot = `apps/${projectName}`;
beforeEach(async () => {
// Clean up any previous dry run simulations
process.argv = process.argv.filter(
(a) => !['--dry-run', '--dryRun', '-d'].includes(a)
);
host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(host, projectName, {
root: projectRoot,
projectType: 'application',
targets: {
/**
* LINT TARGET CONFIG - BEFORE CONVERSION
*
* TSLint executor configured for the project
*/
lint: {
executor: '@angular-devkit/build-angular:tslint',
options: {
exclude: ['**/node_modules/**', `!${projectRoot}/**/*`],
tsConfig: [`${projectRoot}/tsconfig.app.json`],
},
},
},
/**
* PROJECT-LEVEL GENERATOR CONFIG - BEFORE CONVERSION
*
* Default set to tslint, using shorthand syntax
*/
generators: {
'@nx/angular:library': {
linter: 'tslint',
},
'@nx/angular:application': {
e2eTestRunner: 'cypress',
linter: 'eslint',
unitTestRunner: 'jest',
},
},
});
});
it('should throw if --dry-run is set', () => {
writeJson(host, 'tslint.json', {});
writeJson(host, `${projectRoot}/tslint.json`, {});
process.argv.push('--dry-run');
expect(
() =>
new ProjectConverter({
host,
projectName,
ignoreExistingTslintConfig: false,
eslintInitializer: () => undefined,
})
).toThrowErrorMatchingSnapshot();
});
it('should throw if --dryRun is set', () => {
writeJson(host, 'tslint.json', {});
writeJson(host, `${projectRoot}/tslint.json`, {});
process.argv.push('--dryRun');
expect(
() =>
new ProjectConverter({
host,
projectName,
ignoreExistingTslintConfig: false,
eslintInitializer: () => undefined,
})
).toThrowErrorMatchingSnapshot();
});
it('should throw if -d is set', () => {
writeJson(host, 'tslint.json', {});
writeJson(host, `${projectRoot}/tslint.json`, {});
process.argv.push('-d');
expect(
() =>
new ProjectConverter({
host,
projectName,
ignoreExistingTslintConfig: false,
eslintInitializer: () => undefined,
})
).toThrowErrorMatchingSnapshot();
});
it('should throw if no root tslint.json is found', () => {
expect(
() =>
new ProjectConverter({
host,
projectName,
ignoreExistingTslintConfig: false,
eslintInitializer: () => undefined,
})
).toThrowErrorMatchingSnapshot();
});
it('should not throw if no root tslint.json is found but ignore is set', () => {
expect(
() =>
new ProjectConverter({
host,
projectName,
ignoreExistingTslintConfig: true,
eslintInitializer: () => undefined,
})
).not.toThrow();
});
it('should not throw if no project tslint.json is found', () => {
writeJson(host, 'tslint.json', {});
expect(
() =>
new ProjectConverter({
host,
projectName,
ignoreExistingTslintConfig: false,
eslintInitializer: () => undefined,
})
).not.toThrow();
});
it('should not throw when not in dry-run and config files successfully found', () => {
writeJson(host, 'tslint.json', {});
writeJson(host, `${projectRoot}/tslint.json`, {});
expect(
() =>
new ProjectConverter({
host,
projectName,
ignoreExistingTslintConfig: false,
eslintInitializer: () => undefined,
})
).not.toThrow();
});
describe('ignoreExistingTslintConfig', () => {
describe('ignoreExistingTslintConfig: false', () => {
it('should use existing TSLint configs and merge their converted ESLint equivalents with recommended ESLint configs', async () => {
const { convertTSLintConfig } = require('./utils');
(convertTSLintConfig as jest.Mock).mockClear();
writeJson(host, 'tslint.json', {});
writeJson(host, `${projectRoot}/tslint.json`, {});
const projectConverterDiscardFalse = new ProjectConverter({
host,
projectName,
ignoreExistingTslintConfig: false,
eslintInitializer: () => {
writeJson(host, '.eslintrc.json', {
rules: {
'some-recommended-root-eslint-rule': 'error',
},
});
writeJson(host, `${projectRoot}/.eslintrc.json`, {
rules: {
'some-recommended-project-eslint-rule': 'error',
},
});
return Promise.resolve(undefined);
},
});
await projectConverterDiscardFalse.initESLint();
// Existing ESLint rules from init stage will be merged with converted rules
await projectConverterDiscardFalse.convertRootTSLintConfig((j) => j);
expect(readJson(host, '.eslintrc.json')).toMatchSnapshot();
// Existing ESLint rules from init stage will be merged with converted rules
await projectConverterDiscardFalse.convertProjectConfig((j) => j);
expect(
readJson(host, `${projectRoot}/.eslintrc.json`)
).toMatchSnapshot();
expect(convertTSLintConfig).toHaveBeenCalledTimes(2);
});
});
describe('ignoreExistingTslintConfig: true', () => {
it('should ignore existing TSLint configs and just reset the project to use recommended ESLint configs', async () => {
const { convertTSLintConfig } = require('./utils');
(convertTSLintConfig as jest.Mock).mockClear();
writeJson(host, 'tslint.json', {});
writeJson(host, `${projectRoot}/tslint.json`, {});
const projectConverterDiscardTrue = new ProjectConverter({
host,
projectName,
ignoreExistingTslintConfig: true,
eslintInitializer: () => {
writeJson(host, '.eslintrc.json', {
rules: {
'some-recommended-root-eslint-rule': 'error',
},
});
writeJson(host, `${projectRoot}/.eslintrc.json`, {
rules: {
'some-recommended-project-eslint-rule': 'error',
},
});
return Promise.resolve(undefined);
},
});
await projectConverterDiscardTrue.initESLint();
// Should not contain any converted rules, just existing ESLint rules from init stage
await projectConverterDiscardTrue.convertRootTSLintConfig((j) => j);
expect(readJson(host, '.eslintrc.json')).toMatchSnapshot();
// Should not contain any converted rules, just existing ESLint rules from init stage
await projectConverterDiscardTrue.convertProjectConfig((j) => j);
expect(
readJson(host, `${projectRoot}/.eslintrc.json`)
).toMatchSnapshot();
expect(convertTSLintConfig).not.toHaveBeenCalled();
});
});
});
describe('setDefaults()', () => {
it('should save in nx.json', async () => {
writeJson(host, 'tslint.json', {});
writeJson(host, `${projectRoot}/tslint.json`, {});
const projectConverter = new ProjectConverter({
host,
projectName,
ignoreExistingTslintConfig: false,
eslintInitializer: () => undefined,
});
const nxJson = readJson<NxJsonConfiguration>(host, 'nx.json');
nxJson.generators = {
'@nx/angular': {
application: {
linter: 'tslint',
},
library: {
linter: 'tslint',
},
},
};
writeJson(host, 'nx.json', nxJson);
// BEFORE - no entry for convert-tslint-to-eslint wthin @nx/angular generators
expect(readJson(host, 'nx.json')).toMatchSnapshot();
projectConverter.setDefaults('@nx/angular', {
ignoreExistingTslintConfig: true,
removeTSLintIfNoMoreTSLintTargets: true,
});
// AFTER (1) - convert-tslint-to-eslint wthin @nx/angular generators has removeTSLintIfNoMoreTSLintTargets set to true
expect(readJson(host, 'nx.json')).toMatchSnapshot();
projectConverter.setDefaults('@nx/angular', {
ignoreExistingTslintConfig: false,
removeTSLintIfNoMoreTSLintTargets: false,
});
// AFTER (2) - convert-tslint-to-eslint wthin @nx/angular generators has removeTSLintIfNoMoreTSLintTargets set to false
expect(readJson(host, 'nx.json')).toMatchSnapshot();
});
});
describe('removeTSLintFromWorkspace()', () => {
it('should remove all relevant traces of TSLint from the workspace', async () => {
writeJson(host, 'tslint.json', {});
writeJson(host, `${projectRoot}/tslint.json`, {});
const projectConverter = new ProjectConverter({
host,
projectName,
ignoreExistingTslintConfig: false,
eslintInitializer: () => undefined,
});
await addDependenciesToPackageJson(
host,
{},
{
codelyzer: 'latest',
tslint: 'latest',
}
)();
const nxJson = readJson<NxJsonConfiguration>(host, 'nx.json');
// Not using shorthand syntax this time
nxJson.generators = {
'@nx/angular': {
application: {
linter: 'tslint',
},
library: {
linter: 'tslint',
},
},
};
writeJson(host, 'nx.json', nxJson);
// BEFORE - tslint and codelyzer are present
expect(readJson(host, 'package.json')).toMatchSnapshot();
// BEFORE - tslint set as both global linter for @nx/angular generators
expect(readJson(host, 'nx.json')).toMatchSnapshot();
expect(readProjectConfiguration(host, projectName)).toMatchSnapshot();
await projectConverter.removeTSLintFromWorkspace()();
// AFTER - it should remove tslint and codelyzer
expect(readJson(host, 'package.json')).toMatchSnapshot();
// AFTER - generators config from global project-level settings removed (because eslint is always default)
expect(readJson(host, 'nx.json')).toMatchSnapshot();
// AFTER - generators config from project-level settings removed (because eslint is always default)
expect(readProjectConfiguration(host, projectName)).toMatchSnapshot();
});
it('should remove the entry in generators for convert-tslint-to-eslint because it is no longer needed', async () => {
writeJson(host, 'tslint.json', {});
writeJson(host, `${projectRoot}/tslint.json`, {});
const projectConverter = new ProjectConverter({
host,
projectName,
ignoreExistingTslintConfig: false,
eslintInitializer: () => undefined,
});
const nxJson = readJson<NxJsonConfiguration>(host, 'nx.json');
nxJson.generators = {
'@nx/angular': {
'convert-tslint-to-eslint': {
removeTSLintIfNoMoreTSLintTargets: true,
},
},
};
writeJson(host, 'nx.json', nxJson);
// BEFORE - convert-tslint-to-eslint wthin @nx/angular generators has a value for removeTSLintIfNoMoreTSLintTargets
expect(readJson(host, 'nx.json')).toMatchSnapshot();
await projectConverter.removeTSLintFromWorkspace()();
// AFTER - generators config no longer has a reference to convert-tslint-to-eslint
expect(readJson(host, 'nx.json')).toMatchSnapshot();
});
});
});