nx/e2e/node/src/node.test.ts
Chau Tran 142ed2eead
feat(js): inline non-buildable libs for tsc and swc (#12280)
Co-authored-by: Chau Tran <chautran@10.0.0.10>
2022-10-07 09:46:43 +07:00

505 lines
15 KiB
TypeScript

import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import {
checkFilesDoNotExist,
checkFilesExist,
cleanupProject,
createFile,
expectJestTestsToPass,
killPorts,
newProject,
packageInstall,
promisifiedTreeKill,
readFile,
runCLI,
runCLIAsync,
runCommandUntil,
tmpProjPath,
uniq,
updateFile,
updateProjectConfig,
} from '@nrwl/e2e/utils';
import { exec, execSync } from 'child_process';
import * as http from 'http';
import { satisfies } from 'semver';
function getData(port, path = '/api'): Promise<any> {
return new Promise((resolve) => {
http.get(`http://localhost:${port}${path}`, (res) => {
expect(res.statusCode).toEqual(200);
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.once('end', () => {
try {
resolve(JSON.parse(data));
} catch (e) {
resolve(data);
}
});
});
});
}
describe('Node Applications', () => {
beforeEach(() => newProject());
afterEach(() => cleanupProject());
it('should be able to generate an empty application', async () => {
const nodeapp = uniq('nodeapp');
runCLI(`generate @nrwl/node:app ${nodeapp} --linter=eslint`);
const lintResults = runCLI(`lint ${nodeapp}`);
expect(lintResults).toContain('All files pass linting.');
updateFile(`apps/${nodeapp}/src/main.ts`, `console.log('Hello World!');`);
await runCLIAsync(`build ${nodeapp}`);
checkFilesExist(`dist/apps/${nodeapp}/main.js`);
const result = execSync(`node dist/apps/${nodeapp}/main.js`, {
cwd: tmpProjPath(),
}).toString();
expect(result).toContain('Hello World!');
}, 300000);
it('should be able to generate the correct outputFileName in options', async () => {
const nodeapp = uniq('nodeapp');
runCLI(`generate @nrwl/node:app ${nodeapp} --linter=eslint`);
updateProjectConfig(nodeapp, (config) => {
config.targets.build.options.outputFileName = 'index.js';
return config;
});
await runCLIAsync(`build ${nodeapp}`);
checkFilesExist(`dist/apps/${nodeapp}/index.js`);
}, 300000);
it('should be able to generate an empty application with additional entries', async () => {
const nodeapp = uniq('nodeapp');
runCLI(`generate @nrwl/node:app ${nodeapp} --linter=eslint`);
const lintResults = runCLI(`lint ${nodeapp}`);
expect(lintResults).toContain('All files pass linting.');
updateProjectConfig(nodeapp, (config) => {
config.targets.build.options.additionalEntryPoints = [
{
entryName: 'additional-main',
entryPath: `apps/${nodeapp}/src/additional-main.ts`,
},
];
return config;
});
updateFile(
`apps/${nodeapp}/src/additional-main.ts`,
`console.log('Hello Additional World!');`
);
updateFile(`apps/${nodeapp}/src/main.ts`, `console.log('Hello World!');`);
await runCLIAsync(`build ${nodeapp}`);
checkFilesExist(
`dist/apps/${nodeapp}/main.js`,
`dist/apps/${nodeapp}/additional-main.js`
);
const result = execSync(`node dist/apps/${nodeapp}/main.js`, {
cwd: tmpProjPath(),
}).toString();
expect(result).toContain('Hello World!');
const additionalResult = execSync(
`node dist/apps/${nodeapp}/additional-main.js`,
{
cwd: tmpProjPath(),
}
).toString();
expect(additionalResult).toContain('Hello Additional World!');
}, 60000);
it('should be able to generate an express application', async () => {
const nodeapp = uniq('nodeapp');
const originalEnvPort = process.env.port;
const port = 3333;
process.env.port = `${port}`;
runCLI(`generate @nrwl/express:app ${nodeapp} --linter=eslint`);
const lintResults = runCLI(`lint ${nodeapp}`);
expect(lintResults).toContain('All files pass linting.');
updateFile(
`apps/${nodeapp}/src/app/test.spec.ts`,
`
describe('test', () => {
it('should work', () => {
expect(true).toEqual(true);
})
})
`
);
const jestResult = runCLI(`test ${nodeapp}`);
expect(jestResult).toContain('Successfully ran target test');
// checking serve
updateFile(`apps/${nodeapp}/src/assets/file.txt`, `Test`);
const p = await runCommandUntil(`serve ${nodeapp}`, (output) =>
output.includes(`Listening at http://localhost:${port}`)
);
let result = await getData(port);
expect(result.message).toMatch(`Welcome to ${nodeapp}!`);
result = await getData(port, '/assets/file.txt');
expect(result).toMatch(`Test`);
try {
await promisifiedTreeKill(p.pid, 'SIGKILL');
await killPorts(port);
} finally {
process.env.port = originalEnvPort;
}
}, 120_000);
xit('should be able to generate a nest application', async () => {
const nestapp = uniq('nestapp');
const port = 3335;
runCLI(`generate @nrwl/nest:app ${nestapp} --linter=eslint`);
const lintResults = runCLI(`lint ${nestapp}`);
expect(lintResults).toContain('All files pass linting.');
updateFile(`apps/${nestapp}/src/assets/file.txt`, ``);
const jestResult = await runCLIAsync(`test ${nestapp}`);
expect(jestResult.combinedOutput).toContain(
'Test Suites: 2 passed, 2 total'
);
await runCLIAsync(`build ${nestapp}`);
checkFilesExist(
`dist/apps/${nestapp}/main.js`,
`dist/apps/${nestapp}/assets/file.txt`,
`dist/apps/${nestapp}/main.js.map`
);
const server = exec(`node ./dist/apps/${nestapp}/main.js`, {
cwd: tmpProjPath(),
});
expect(server).toBeTruthy();
// checking build
await new Promise((resolve) => {
server.stdout.on('data', async (data) => {
const message = data.toString();
if (message.includes(`Listening at http://localhost:${port}`)) {
const result = await getData(port);
expect(result.message).toEqual(`Welcome to ${nestapp}!`);
server.kill();
resolve(null);
}
});
});
// checking serve
const p = await runCommandUntil(
`serve ${nestapp} --port=${port}`,
(output) => {
process.stdout.write(output);
return output.includes(`Listening at http://localhost:${port}`);
}
);
const result = await getData(port);
expect(result.message).toEqual(`Welcome to ${nestapp}!`);
try {
await promisifiedTreeKill(p.pid, 'SIGKILL');
expect(await killPorts(port)).toBeTruthy();
} catch (err) {
expect(err).toBeFalsy();
}
}, 120000);
});
describe('Build Node apps', () => {
beforeEach(() => newProject());
it('should generate a package.json with the `--generatePackageJson` flag', async () => {
const scope = newProject();
const nestapp = uniq('nestapp');
runCLI(`generate @nrwl/nest:app ${nestapp} --linter=eslint`);
await runCLIAsync(`build ${nestapp} --generatePackageJson`);
checkFilesExist(`dist/apps/${nestapp}/package.json`);
const packageJson = JSON.parse(
readFile(`dist/apps/${nestapp}/package.json`)
);
expect(packageJson).toEqual(
expect.objectContaining({
main: 'main.js',
name: expect.any(String),
version: '0.0.1',
})
);
expect(
satisfies(packageJson.dependencies['@nestjs/common'], '^9.0.0')
).toBeTruthy();
expect(
satisfies(packageJson.dependencies['@nestjs/core'], '^9.0.0')
).toBeTruthy();
expect(
satisfies(packageJson.dependencies['@nestjs/platform-express'], '^9.0.0')
).toBeTruthy();
expect(
satisfies(packageJson.dependencies['reflect-metadata'], '^0.1.13')
).toBeTruthy();
expect(satisfies(packageJson.dependencies['rxjs'], '^7.0.0')).toBeTruthy();
expect(satisfies(packageJson.dependencies['tslib'], '^2.3.0')).toBeTruthy();
const nodeapp = uniq('nodeapp');
runCLI(`generate @nrwl/node:app ${nodeapp}`);
const jslib = uniq('jslib');
runCLI(`generate @nrwl/js:lib ${jslib} --buildable`);
updateFile(
`apps/${nodeapp}/src/main.ts`,
`
import { ${jslib} } from '@${scope}/${jslib}';
console.log('Hello World!');
${jslib}();
`
);
await runCLIAsync(`build ${nodeapp} --generate-package-json`);
checkFilesExist(`dist/apps/${nestapp}/package.json`);
const nodeAppPackageJson = JSON.parse(
readFile(`dist/apps/${nodeapp}/package.json`)
);
expect(nodeAppPackageJson['dependencies']['tslib']).toBeTruthy();
}, 300000);
it('should remove previous output before building with the --deleteOutputPath option set', async () => {
const appName = uniq('app');
runCLI(`generate @nrwl/node:app ${appName} --no-interactive`);
// deleteOutputPath should default to true
createFile(`dist/apps/${appName}/_should_remove.txt`);
createFile(`dist/apps/_should_not_remove.txt`);
checkFilesExist(
`dist/apps/${appName}/_should_remove.txt`,
`dist/apps/_should_not_remove.txt`
);
runCLI(`build ${appName} --outputHashing none`); // no explicit deleteOutputPath option set
checkFilesDoNotExist(`dist/apps/${appName}/_should_remove.txt`);
checkFilesExist(`dist/apps/_should_not_remove.txt`);
// Explicitly set `deleteOutputPath` to true
createFile(`dist/apps/${appName}/_should_remove.txt`);
createFile(`dist/apps/_should_not_remove.txt`);
checkFilesExist(
`dist/apps/${appName}/_should_remove.txt`,
`dist/apps/_should_not_remove.txt`
);
runCLI(`build ${appName} --outputHashing none --deleteOutputPath`);
checkFilesDoNotExist(`dist/apps/${appName}/_should_remove.txt`);
checkFilesExist(`dist/apps/_should_not_remove.txt`);
// Explicitly set `deleteOutputPath` to false
createFile(`dist/apps/${appName}/_should_keep.txt`);
createFile(`dist/apps/_should_keep.txt`);
runCLI(`build ${appName} --deleteOutputPath=false --outputHashing none`);
checkFilesExist(`dist/apps/${appName}/_should_keep.txt`);
checkFilesExist(`dist/apps/_should_keep.txt`);
}, 120000);
describe('NestJS', () => {
it('should have plugin output if specified in `tsPlugins`', async () => {
newProject();
const nestapp = uniq('nestapp');
runCLI(`generate @nrwl/nest:app ${nestapp} --linter=eslint`);
packageInstall('@nestjs/swagger', undefined, '^6.0.0');
updateProjectConfig(nestapp, (config) => {
config.targets.build.options.tsPlugins = ['@nestjs/swagger/plugin'];
return config;
});
updateFile(
`apps/${nestapp}/src/app/foo.dto.ts`,
`
export class FooDto {
foo: string;
bar: number;
}`
);
updateFile(
`apps/${nestapp}/src/app/app.controller.ts`,
`
import { Controller, Get } from '@nestjs/common';
import { FooDto } from './foo.dto';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getData() {
return this.appService.getData();
}
@Get('foo')
getFoo(): Promise<FooDto> {
return Promise.resolve({
foo: 'foo',
bar: 123
})
}
}`
);
await runCLIAsync(`build ${nestapp}`);
const mainJs = readFile(`dist/apps/${nestapp}/main.js`);
expect(mainJs).toContain('FooDto');
expect(mainJs).toContain('_OPENAPI_METADATA_FACTORY');
}, 300000);
});
});
describe('nest libraries', function () {
beforeEach(() => newProject());
it('should be able to generate a nest library', async () => {
const nestlib = uniq('nestlib');
runCLI(`generate @nrwl/nest:lib ${nestlib}`);
const jestConfigContent = readFile(`libs/${nestlib}/jest.config.ts`);
expect(stripIndents`${jestConfigContent}`).toEqual(
stripIndents`/* eslint-disable */
export default {
displayName: '${nestlib}',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/libs/${nestlib}',
};
`
);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('All files pass linting.');
}, 60000);
it('should be able to generate a nest library w/ service', async () => {
const nestlib = uniq('nestlib');
runCLI(`generate @nrwl/nest:lib ${nestlib} --service`);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('All files pass linting.');
const jestResult = await runCLIAsync(`test ${nestlib}`);
expect(jestResult.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
}, 200000);
it('should be able to generate a nest library w/ controller', async () => {
const nestlib = uniq('nestlib');
runCLI(`generate @nrwl/nest:lib ${nestlib} --controller`);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('All files pass linting.');
const jestResult = await runCLIAsync(`test ${nestlib}`);
expect(jestResult.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
}, 200000);
it('should be able to generate a nest library w/ controller and service', async () => {
const nestlib = uniq('nestlib');
runCLI(`generate @nrwl/nest:lib ${nestlib} --controller --service`);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('All files pass linting.');
const jestResult = await runCLIAsync(`test ${nestlib}`);
expect(jestResult.combinedOutput).toContain(
'Test Suites: 2 passed, 2 total'
);
}, 200000);
it('should have plugin output if specified in `transformers`', async () => {
newProject();
const nestlib = uniq('nestlib');
runCLI(`generate @nrwl/nest:lib ${nestlib} --buildable`);
packageInstall('@nestjs/swagger', undefined, '~6.0.0');
updateProjectConfig(nestlib, (config) => {
config.targets.build.options.transformers = [
{
name: '@nestjs/swagger/plugin',
options: {
dtoFileNameSuffix: ['.model.ts'],
},
},
];
return config;
});
updateFile(
`libs/${nestlib}/src/lib/foo.model.ts`,
`
export class FooModel {
foo: string;
bar: number;
}`
);
await runCLIAsync(`build ${nestlib}`);
const fooModelJs = readFile(`dist/libs/${nestlib}/src/lib/foo.model.js`);
expect(stripIndents`${fooModelJs}`).toContain(
stripIndents`
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FooModel = void 0;
const openapi = require("@nestjs/swagger");
class FooModel {
static _OPENAPI_METADATA_FACTORY() {
return { foo: { required: true, type: () => String }, bar: { required: true, type: () => Number } };
}
}
exports.FooModel = FooModel;
//# sourceMappingURL=foo.model.js.map
`
);
}, 300000);
it('should run default jest tests', async () => {
await expectJestTestsToPass('@nrwl/node:lib');
}, 100000);
});