Files
jest/e2e/__tests__/multiProjectRunner.test.ts
2025-02-27 08:06:11 +00:00

613 lines
18 KiB
TypeScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {tmpdir} from 'os';
import * as path from 'path';
import {cleanup, extractSummary, sortLines, writeFiles} from '../Utils';
import runJest, {getConfig} from '../runJest';
const DIR = path.resolve(tmpdir(), 'multi-project-runner-test');
const SAMPLE_FILE_CONTENT = 'module.exports = {};';
beforeEach(() => cleanup(DIR));
afterEach(() => cleanup(DIR));
test("--listTests doesn't duplicate the test files", () => {
writeFiles(DIR, {
'.watchmanconfig': '{}',
'/project1.js': "module.exports = {rootDir: './', displayName: 'BACKEND'}",
'/project2.js': "module.exports = {rootDir: './', displayName: 'BACKEND'}",
'__tests__/inBothProjectsTest.js': "test('test', () => {});",
'package.json': JSON.stringify({
jest: {projects: ['<rootDir>/project1.js', '<rootDir>/project2.js']},
}),
});
const {stdout} = runJest(DIR, ['--listTests']);
expect(stdout.split('\n')).toHaveLength(1);
expect(stdout).toMatch('inBothProjectsTest.js');
});
test('can pass projects or global config', () => {
writeFiles(DIR, {
'.watchmanconfig': '{}',
'base_config.js': `
module.exports = {
haste: {
hasteImplModulePath: '<rootDir>/hasteImpl.js',
},
};
`,
'hasteImpl.js': `
const path = require('path');
module.exports = {
getHasteName(filename) {
return filename
.substr(filename.lastIndexOf(path.sep) + 1)
.replace(/\\.js$/, '');
},
};
`,
'package.json': '{}',
'project1/__tests__/file1.test.js': `
const file1 = require('file1');
test('file1', () => {});
`,
'project1/file1.js': SAMPLE_FILE_CONTENT,
'project1/jest.config.js': `module.exports = {rootDir: './', displayName: 'BACKEND', haste: {
hasteImplModulePath: '<rootDir>/../hasteImpl.js',
},}`,
'project2/__tests__/file1.test.js': `
const file1 = require('file1');
test('file1', () => {});
`,
'project2/file1.js': SAMPLE_FILE_CONTENT,
'project2/jest.config.js': `module.exports = {rootDir: './', haste: {
hasteImplModulePath: '<rootDir>/../hasteImpl.js',
},}`,
'project3/__tests__/file1.test.js': `
const file1 = require('file1');
test('file1', () => {});
`,
'project3/file1.js': SAMPLE_FILE_CONTENT,
'project3/jest.config.js': `module.exports = {rootDir: './', displayName: 'UI', haste: {
hasteImplModulePath: '<rootDir>/../hasteImpl.js',
},}`,
});
let stderr;
({stderr} = runJest(DIR, ['--no-watchman', '--config', 'base_config.js']));
expect(stderr).toMatch(
'The name `file1` was looked up in the Haste module map. It cannot be resolved, because there exists several different files',
);
expect(extractSummary(stderr).summary).toMatchSnapshot();
writeFiles(DIR, {
'global_config.js': `
module.exports = {
projects: ['project1/', 'project2/', 'project3/'],
haste: {
hasteImplModulePath: '<rootDir>/hasteImpl.js',
},
};
`,
});
({stderr} = runJest(DIR, [
'--no-watchman',
'-i',
'--projects',
'project1',
'project2',
'project3',
'--config',
'base_config.js',
]));
const result1 = extractSummary(stderr);
expect(result1.summary).toMatchSnapshot();
expect(sortLines(result1.rest)).toMatchSnapshot();
({stderr} = runJest(DIR, [
'--no-watchman',
'-i',
'--config',
'global_config.js',
]));
const result2 = extractSummary(stderr);
expect(result2.summary).toMatchSnapshot();
expect(sortLines(result2.rest)).toMatchSnapshot();
// make sure different ways of passing projects work exactly the same
expect(result1.summary).toBe(result2.summary);
expect(sortLines(result1.rest)).toBe(sortLines(result2.rest));
});
test('"No tests found" message for projects', () => {
writeFiles(DIR, {
'.watchmanconfig': '{}',
'package.json': '{}',
'project1/__tests__/file1.test.js': `
const file1 = require('../file1');
test('file1', () => {});
`,
'project1/file1.js': SAMPLE_FILE_CONTENT,
'project1/jest.config.js': "module.exports = {rootDir: './'}",
'project2/__tests__/file1.test.js': `
const file1 = require('../file1');
test('file1', () => {});
`,
'project2/file1.js': SAMPLE_FILE_CONTENT,
'project2/jest.config.js': "module.exports = {rootDir: './'}",
});
const {stdout: verboseOutput} = runJest(DIR, [
'--no-watchman',
'xyz321',
'--verbose',
'--projects',
'project1',
'project2',
]);
expect(verboseOutput).toContain('Pattern: xyz321 - 0 matches');
const {stdout} = runJest(DIR, [
'--no-watchman',
'xyz321',
'--projects',
'project1',
'project2',
]);
expect(stdout).toContain(
' 6 files checked across 2 projects. ' +
'Run with `--verbose` for more details.',
);
});
test.each([{projectPath: 'packages/somepackage'}, {projectPath: 'packages/*'}])(
'allows a single non-root project',
({projectPath}: {projectPath: string}) => {
writeFiles(DIR, {
'package.json': `
{
"jest": {
"testMatch": ["<rootDir>/packages/somepackage/test.js"],
"projects": [
"${projectPath}"
]
}
}
`,
'packages/somepackage/package.json': `
{
"jest": {
"displayName": "somepackage"
}
}
`,
'packages/somepackage/test.js': `
test('1+1', () => {
expect(1).toBe(1);
});
`,
});
const {stdout, stderr, exitCode} = runJest(DIR, ['--no-watchman']);
expect(stderr).toContain('PASS somepackage packages/somepackage/test.js');
expect(stderr).toContain('Test Suites: 1 passed, 1 total');
expect(stdout).toBe('');
expect(exitCode).toBe(0);
},
);
test.each([
{displayName: 'p1', projectPath: 'packages/p1'},
{displayName: 'p2', projectPath: 'packages/p2'},
])(
'correctly runs a single non-root project',
({projectPath, displayName}: {projectPath: string; displayName: string}) => {
writeFiles(DIR, {
'package.json': `
{
"jest": {
"projects": [
"${projectPath}"
]
}
}
`,
'packages/p1/package.json': `
{
"jest": {
"displayName": "p1"
}
}
`,
'packages/p1/test.js': `
test('1+1', () => {
expect(1).toBe(1);
});
`,
'packages/p2/package.json': `
{
"jest": {
"displayName": "p2"
}
}
`,
'packages/p2/test.js': `
test('1+1', () => {
expect(1).toBe(1);
});
`,
});
const {stdout, stderr, exitCode} = runJest(DIR, ['--no-watchman']);
expect(stderr).toContain(`PASS ${displayName} ${projectPath}/test.js`);
expect(stderr).toContain('Test Suites: 1 passed, 1 total');
expect(stdout).toBe('');
expect(exitCode).toBe(0);
},
);
test('projects can be workspaces with non-JS/JSON files', () => {
writeFiles(DIR, {
'package.json': JSON.stringify({
jest: {
projects: ['packages/*'],
},
}),
'packages/README.md': '# Packages README',
'packages/project1/README.md': '# Project1 README',
'packages/project1/__tests__/file1.test.js': `
const file1 = require('../file1');
test('file1', () => {});
`,
'packages/project1/file1.js': SAMPLE_FILE_CONTENT,
'packages/project1/package.json': '{}',
'packages/project2/__tests__/file2.test.js': `
const file2 = require('../file2');
test('file2', () => {});
`,
'packages/project2/file2.js': SAMPLE_FILE_CONTENT,
'packages/project2/package.json': '{}',
});
const {exitCode, stdout, stderr} = runJest(DIR, ['--no-watchman']);
expect(stderr).toContain('Test Suites: 2 passed, 2 total');
expect(stderr).toContain('PASS packages/project1/__tests__/file1.test.js');
expect(stderr).toContain('PASS packages/project2/__tests__/file2.test.js');
expect(stderr).toContain('Ran all test suites in 2 projects.');
expect(stdout).toBe('');
expect(exitCode).toBe(0);
});
test('objects in project configuration', () => {
writeFiles(DIR, {
'__tests__/file1.test.js': `
test('foo', () => {});
`,
'__tests__/file2.test.js': `
test('bar', () => {});
`,
'jest.config.js': `module.exports = {
projects: [
{ testMatch: ['<rootDir>/__tests__/file1.test.js'] },
{ testMatch: ['<rootDir>/__tests__/file2.test.js'] },
]
};`,
'package.json': '{}',
});
const {stdout, stderr, exitCode} = runJest(DIR, ['--no-watchman']);
expect(stderr).toContain('Test Suites: 2 passed, 2 total');
expect(stderr).toContain('PASS __tests__/file1.test.js');
expect(stderr).toContain('PASS __tests__/file2.test.js');
expect(stderr).toContain('Ran all test suites in 2 projects.');
expect(stdout).toBe('');
expect(exitCode).toBe(0);
});
test('allows a single project', () => {
writeFiles(DIR, {
'__tests__/file1.test.js': `
test('foo', () => {});
`,
'jest.config.js': `module.exports = {
projects: [
{ testMatch: ['<rootDir>/__tests__/file1.test.js'] },
]
};`,
'package.json': '{}',
});
const {stdout, stderr, exitCode} = runJest(DIR, ['--no-watchman']);
expect(stderr).toContain('PASS __tests__/file1.test.js');
expect(stderr).toContain('Test Suites: 1 passed, 1 total');
expect(stdout).toBe('');
expect(exitCode).toBe(0);
});
test('resolves projects and their <rootDir> properly', () => {
writeFiles(DIR, {
'.watchmanconfig': '{}',
'package.json': JSON.stringify({
jest: {
projects: [
'project1.conf.json',
'<rootDir>/project2/project2.conf.json',
],
},
}),
'project1.conf.json': JSON.stringify({
id: 'project1',
rootDir: './project1',
// root dir should be this project's directory
setupFiles: ['<rootDir>/project1_setup.js'],
testEnvironment: 'node',
}),
'project1/__tests__/test.test.js':
"test('project1', () => expect(globalThis.project1).toBe(true))",
'project1/project1_setup.js': 'global.project1 = true;',
'project2/__tests__/test.test.js':
"test('project2', () => expect(globalThis.project2).toBe(true))",
'project2/project2.conf.json': JSON.stringify({
id: 'project2',
rootDir: '../', // root dir is set to the top level
setupFiles: ['<rootDir>/project2/project2_setup.js'], // rootDir should be of the
testEnvironment: 'node',
testPathIgnorePatterns: ['project1'],
}),
'project2/project2_setup.js': 'global.project2 = true;',
});
let stderr;
({stderr} = runJest(DIR, ['--no-watchman']));
expect(stderr).toMatch('Ran all test suites in 2 projects.');
expect(stderr).toMatch('PASS project1/__tests__/test.test.js');
expect(stderr).toMatch('PASS project2/__tests__/test.test.js');
// Use globs
writeFiles(DIR, {
'dir1/random_file': '',
'dir2/random_file': '',
'package.json': JSON.stringify({
jest: {
projects: ['**/*.conf.json'],
},
}),
});
({stderr} = runJest(DIR, ['--no-watchman']));
expect(stderr).toMatch('Ran all test suites in 2 projects.');
expect(stderr).toMatch('PASS project1/__tests__/test.test.js');
expect(stderr).toMatch('PASS project2/__tests__/test.test.js');
// Include two projects that will resolve to the same config
writeFiles(DIR, {
'dir1/random_file': '',
'dir2/random_file': '',
'package.json': JSON.stringify({
jest: {
projects: [
'dir1',
'dir2',
'project1.conf.json',
'<rootDir>/project2/project2.conf.json',
],
},
}),
});
({stderr} = runJest(DIR, ['--no-watchman']));
expect(stderr).toMatch(
/Whoops! Two projects resolved to the same config path/,
);
expect(stderr).toMatch(`${path.join(DIR, 'package.json')}`);
expect(stderr).toMatch(/Project 1|2: dir1/);
expect(stderr).toMatch(/Project 1|2: dir2/);
// project with a directory/file that does not exist
writeFiles(DIR, {
'package.json': JSON.stringify({
jest: {
projects: [
'banana',
'project1.conf.json',
'<rootDir>/project2/project2.conf.json',
],
},
}),
});
({stderr} = runJest(DIR, ['--no-watchman']));
expect(stderr).toMatch(
"Can't find a root directory while resolving a config file path.",
);
expect(stderr).toMatch(/banana/);
});
test('Does transform files with the corresponding project transformer', () => {
writeFiles(DIR, {
'.watchmanconfig': '{}',
'file.js': SAMPLE_FILE_CONTENT,
'package.json': '{}',
'project1/__tests__/project1.test.js': `
const file = require('../../file.js');
test('file', () => expect(file).toBe('PROJECT1'));
`,
'project1/jest.config.js': `
module.exports = {
rootDir: './',
transform: {'file\\.js': './transformer.js'},
};`,
'project1/transformer.js': `
module.exports = {
process: () => ({code: 'module.exports = "PROJECT1";'}),
getCacheKey: () => 'PROJECT1_CACHE_KEY',
}
`,
'project2/__tests__/project2.test.js': `
const file = require('../../file.js');
test('file', () => expect(file).toBe('PROJECT2'));
`,
'project2/jest.config.js': `
module.exports = {
rootDir: './',
transform: {'file\\.js': './transformer.js'},
};`,
'project2/transformer.js': `
module.exports = {
process: () => ({code: 'module.exports = "PROJECT2";'}),
getCacheKey: () => 'PROJECT2_CACHE_KEY',
}
`,
});
const {stderr} = runJest(DIR, [
'--no-watchman',
'-i',
'--projects',
'project1',
'project2',
]);
expect(stderr).toMatch('Ran all test suites in 2 projects.');
expect(stderr).toMatch('PASS project1/__tests__/project1.test.js');
expect(stderr).toMatch('PASS project2/__tests__/project2.test.js');
});
describe("doesn't bleed module file extensions resolution with multiple workers", () => {
test('external config files', () => {
writeFiles(DIR, {
'.watchmanconfig': '{}',
'file.js': 'module.exports = "file1"',
'file.p2.js': 'module.exports = "file2"',
'package.json': '{}',
'project1/__tests__/project1.test.js': `
const file = require('../../file');
test('file 1', () => expect(file).toBe('file1'));
`,
'project1/jest.config.js': `
module.exports = {
rootDir: '..',
};`,
'project2/__tests__/project2.test.js': `
const file = require('../../file');
test('file 2', () => expect(file).toBe('file2'));
`,
'project2/jest.config.js': `
module.exports = {
rootDir: '..',
moduleFileExtensions: ['p2.js', 'js']
};`,
});
const {configs} = getConfig(DIR, ['--projects', 'project1', 'project2']);
expect(configs).toHaveLength(2);
const [{id: id1}, {id: id2}] = configs;
expect(id1).toEqual(expect.any(String));
expect(id2).toEqual(expect.any(String));
expect(id1).toHaveLength(32);
expect(id2).toHaveLength(32);
expect(id1).not.toEqual(id2);
const {stderr} = runJest(DIR, [
'--no-watchman',
'-w=2',
'--projects',
'project1',
'project2',
]);
expect(stderr).toMatch('Ran all test suites in 2 projects.');
expect(stderr).toMatch('PASS project1/__tests__/project1.test.js');
expect(stderr).toMatch('PASS project2/__tests__/project2.test.js');
});
test('inline config files', () => {
writeFiles(DIR, {
'.watchmanconfig': '{}',
'file.js': 'module.exports = "file1"',
'file.p2.js': 'module.exports = "file2"',
'package.json': JSON.stringify({
jest: {projects: [{}, {moduleFileExtensions: ['p2.js', 'js']}]},
}),
'project1/__tests__/project1.test.js': `
const file = require('../../file');
test('file 1', () => expect(file).toBe('file1'));
`,
'project2/__tests__/project2.test.js': `
const file = require('../../file');
test('file 2', () => expect(file).toBe('file2'));
`,
});
const {configs} = getConfig(DIR);
expect(configs).toHaveLength(2);
const [{id: id1}, {id: id2}] = configs;
expect(id1).toEqual(expect.any(String));
expect(id2).toEqual(expect.any(String));
expect(id1).toHaveLength(32);
expect(id2).toHaveLength(32);
expect(id1).not.toEqual(id2);
const {stderr} = runJest(DIR, ['--no-watchman', '-w=2']);
expect(stderr).toMatch('Ran all test suites in 2 projects.');
expect(stderr).toMatch('PASS project1/__tests__/project1.test.js');
expect(stderr).toMatch('PASS project2/__tests__/project2.test.js');
});
});
describe('Babel config in individual project works in multi-project', () => {
it('Prj-1 works individually', () => {
const result = runJest('multi-project-babel/prj-1');
expect(result.stderr).toMatch('PASS ./index.test.js');
expect(result.exitCode).toBe(0);
});
it('Prj-2 works individually', () => {
const result = runJest('multi-project-babel/prj-2');
expect(result.stderr).toMatch('PASS ./index.test.js');
expect(result.exitCode).toBe(0);
});
it('Prj-3 works individually', () => {
const result = runJest('multi-project-babel/prj-3');
expect(result.stderr).toMatch('PASS src/index.test.js');
expect(result.exitCode).toBe(0);
});
it('Prj-4 works individually', () => {
const result = runJest('multi-project-babel/prj-4');
expect(result.stderr).toMatch('PASS src/index.test.js');
expect(result.exitCode).toBe(0);
});
it('Prj-5 works individually', () => {
const result = runJest('multi-project-babel/prj-5');
expect(result.stderr).toMatch('PASS src/index.test.js');
expect(result.exitCode).toBe(0);
});
it('All project work when running from multiproject', () => {
const result = runJest('multi-project-babel');
expect(result.stderr).toMatch('PASS prj-1/index.test.js');
expect(result.stderr).toMatch('PASS prj-2/index.test.js');
expect(result.stderr).toMatch('PASS prj-3/src/index.test.js');
expect(result.stderr).toMatch('PASS prj-4/src/index.test.js');
expect(result.stderr).toMatch('PASS prj-5/src/index.test.js');
expect(result.stderr).toMatch('PASS prj-3/src/index.test.js');
expect(result.exitCode).toBe(0);
});
});