Skip to content

Commit

Permalink
feat: support generate jest (umijs/father-next#66)
Browse files Browse the repository at this point in the history
* feat: support generate jest

* test: add generate jest test

* refactor: adjust jest.config

* chore: move to tests

* feat: add dumi cov and remove umi cov
  • Loading branch information
miracles1919 authored and PeachScript committed Aug 26, 2022
1 parent a433e54 commit dc9bdc2
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 0 deletions.
100 changes: 100 additions & 0 deletions src/commands/generators/jest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { GeneratorType } from '@umijs/core';
import { logger } from '@umijs/utils';
import { existsSync, writeFileSync } from 'fs';
import { join } from 'path';
import { IApi } from '../../types';
import { GeneratorHelper, promptsExitWhenCancel } from './utils';

export default (api: IApi) => {
api.describe({
key: 'generator:jest',
});

api.registerGenerator({
key: 'jest',
name: 'Enable Jest',
description: 'Setup Jest Configuration',
type: GeneratorType.enable,
checkEnable: () => {
return (
!existsSync(join(api.paths.cwd, 'jest.config.ts')) &&
!existsSync(join(api.paths.cwd, 'jest.config.js'))
);
},
disabledDescription:
'Jest has already enabled. You can remove jest.config.{ts,js}, then run this again to re-setup.',
fn: async () => {
const h = new GeneratorHelper(api);

const res = await promptsExitWhenCancel({
type: 'confirm',
name: 'useRTL',
message: 'Will you use @testing-library/react for UI testing?',
initial: true,
});

const basicDeps = {
jest: '^27',
'@types/jest': '^27',
// we use `jest.config.ts` so jest needs ts and ts-node
typescript: '^4',
'ts-node': '^10',
'@umijs/test': '^4',
};

const deps: Record<string, string> = res.useRTL
? {
...basicDeps,
'@testing-library/react': '^13',
'@testing-library/jest-dom': '^5.16.4',
'@types/testing-library__jest-dom': '^5.14.5',
}
: basicDeps;

h.addDevDeps(deps);
h.addScript('test', 'jest');

if (res.useRTL) {
writeFileSync(
join(api.cwd, 'jest-setup.ts'),
`import '@testing-library/jest-dom';
`.trimStart(),
);
logger.info('Write jest-setup.ts');
}

const collectCoverageFrom = ['src/**/*.{ts,js,tsx,jsx}'];
const hasDumi = Object.keys(api.pkg.devDependencies || {}).includes(
'dumi',
);
if (hasDumi) {
collectCoverageFrom.push(
'!src/.umi/**',
'!src/.umi-test/**',
'!src/.umi-production/**',
);
}

writeFileSync(
join(api.cwd, 'jest.config.ts'),
`
import { Config, createConfig } from '@umijs/test';
export default {
...createConfig(),${
res.useRTL
? `
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],`
: ''
}
collectCoverageFrom: [${collectCoverageFrom.map((v) => `'${v}'`).join(', ')}],
} as Config.InitialOptions;
`.trimStart(),
);

logger.info('Write jest.config.ts');

h.installDeps();
},
});
};
65 changes: 65 additions & 0 deletions src/commands/generators/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
getNpmClient,
installWithNpmClient,
logger,
prompts,
} from '@umijs/utils';
import { writeFileSync } from 'fs';
import { IApi } from '../../types';

export class GeneratorHelper {
constructor(readonly api: IApi) {}

addDevDeps(deps: Record<string, string>) {
const { api } = this;
api.pkg.devDependencies = {
...api.pkg.devDependencies,
...deps,
};
writeFileSync(api.pkgPath, JSON.stringify(api.pkg, null, 2));
logger.info('Write package.json');
}

addScript(name: string, cmd: string) {
const { api } = this;
this.addScriptToPkg(name, cmd);
writeFileSync(api.pkgPath, JSON.stringify(api.pkg, null, 2));
logger.info('Update package.json for scripts');
}

private addScriptToPkg(name: string, cmd: string) {
const { api } = this;
const pkgScriptsName = api.pkg.scripts?.[name];
if (pkgScriptsName && pkgScriptsName !== cmd) {
logger.warn(
`scripts.${name} = "${pkgScriptsName}" already exists, will be overwritten with "${cmd}"!`,
);
}

api.pkg.scripts = {
...api.pkg.scripts,
[name]: cmd,
};
}

installDeps() {
const { api } = this;
const npmClient = getNpmClient({ cwd: api.cwd });
installWithNpmClient({
npmClient,
});
logger.info(`Install dependencies with ${npmClient}`);
}
}

export function promptsExitWhenCancel<T extends string = string>(
questions: prompts.PromptObject<T> | Array<prompts.PromptObject<T>>,
options?: Pick<prompts.Options, 'onSubmit'>,
): Promise<prompts.Answers<T>> {
return prompts(questions, {
...options,
onCancel: () => {
process.exit(1);
},
});
}
1 change: 1 addition & 0 deletions src/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default (api: IApi) => {
require.resolve('./commands/release'),
require.resolve('./commands/version'),
require.resolve('./commands/help'),
require.resolve('./commands/generators/jest'),

// features
require.resolve('./features/configBuilder/configBuilder'),
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/generator/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
87 changes: 87 additions & 0 deletions tests/g.jest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
import path from 'path';
import * as cli from '../src/cli/cli';
import { GeneratorHelper } from '../src/commands/generators/utils';

let useRTL = false;
jest.doMock('../src/commands/generators/utils', () => {
const originalModule = jest.requireActual('../src/commands/generators/utils');
return {
__esModule: true,
...originalModule,
promptsExitWhenCancel: jest.fn(() => ({ useRTL })),
};
});

const mockInstall = jest.fn();
jest
.spyOn(GeneratorHelper.prototype, 'installDeps')
.mockImplementation(mockInstall);

const CASES_DIR = path.join(__dirname, 'fixtures/generator');
describe('jest generator', function () {
process.env.APP_ROOT = path.join(CASES_DIR);
const jestConfPath = path.join(CASES_DIR, 'jest.config.ts');
const jestSetupPath = path.join(CASES_DIR, 'jest-setup.ts');
afterEach(() => {
[jestConfPath, jestSetupPath].forEach((path) => {
if (existsSync(path)) {
unlinkSync(path);
}
});
writeFileSync(path.join(CASES_DIR, 'package.json'), '{}');
});

test('g jest', async () => {
await cli.run({
args: { _: ['g', 'jest'], $0: 'node' },
});

const pkg = JSON.parse(
readFileSync(path.join(CASES_DIR, 'package.json'), 'utf-8'),
);

expect(existsSync(jestConfPath)).toBeTruthy();
expect(pkg['scripts']).toMatchObject({ test: 'jest' });
expect(pkg['devDependencies']).toMatchObject({
jest: '^27',
'@types/jest': '^27',
typescript: '^4',
'ts-node': '^10',
'@umijs/test': '^4',
});
expect(mockInstall).toBeCalled();
});

test('g jest with RTL', async () => {
useRTL = true;

await cli.run({
args: { _: ['g', 'jest'], $0: 'node' },
});

const pkg = JSON.parse(
readFileSync(path.join(CASES_DIR, 'package.json'), 'utf-8'),
);

expect(existsSync(jestSetupPath)).toBeTruthy();
expect(pkg['scripts']).toMatchObject({ test: 'jest' });
expect(pkg['devDependencies']).toMatchObject({
'@testing-library/react': '^13',
'@testing-library/jest-dom': '^5.16.4',
'@types/testing-library__jest-dom': '^5.14.5',
});
expect(mockInstall).toBeCalled();
});

test('warning when jest config exists', async () => {
writeFileSync(jestConfPath, '{}');
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
await cli.run({
args: { _: ['g', 'jest'], $0: 'node' },
});
expect(warnSpy.mock.calls[0][1]).toBe(
'Jest has already enabled. You can remove jest.config.{ts,js}, then run this again to re-setup.',
);
});
});

0 comments on commit dc9bdc2

Please sign in to comment.