Skip to content

Commit

Permalink
Fixes #37637 - Test plugins from foreman core
Browse files Browse the repository at this point in the history
  • Loading branch information
MariaAga committed Jul 9, 2024
1 parent f8b17e2 commit 1c46799
Show file tree
Hide file tree
Showing 9 changed files with 300 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
"noopener",
"noreferrer",
"nowrap",
"npx",
"num",
"numpad",
"operatingsystem",
Expand Down Expand Up @@ -159,6 +160,7 @@
"textarea",
"textfield",
"tfm",
"theforeman",
"timepicker",
"timerdelay",
"timeseries",
Expand Down
4 changes: 4 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
presets: [require.resolve('@theforeman/builder/babel')],
plugins: [require.resolve('babel-plugin-dynamic-import-node')],
};
22 changes: 20 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
"lint:custom": "eslint ./webpack",
"foreman-js:link": "./script/npm_link_foreman_js.sh",
"postlint": "./script/npm_lint_plugins.js",
"test": "tfm-test",
"test": "npx jest --setupFilesAfterEnv ./global_test_setup.js ./core_test_setup.js --testPathIgnorePatterns '/node_modules/' '<rootDir>/.+fixtures.+' --config ./webpack/jest.config.js ",
"test:watch": "tfm-test --watchAll",
"test:current": "tfm-test --watch",
"test:plugins": "./script/npm_test_plugin.js",
"publish-coverage": "tfm-publish-coverage",
"postinstall": "./script/npm_install_plugins.js",
"analyze": "./script/webpack-analyze"
Expand All @@ -31,26 +32,42 @@
"devDependencies": {
"@babel/core": "^7.7.0",
"@theforeman/builder": "^13.1.0",
"@theforeman/vendor-core": "^13.1.0",
"@theforeman/eslint-plugin-foreman": "^13.1.0",
"@theforeman/eslint-plugin-rules": "^13.1.0",
"@theforeman/test": "^13.1.0",
"@theforeman/vendor-dev": "^13.1.0",
"@testing-library/jest-dom": "^5.3.0",
"@testing-library/react": "^10.0.2",
"@testing-library/react-hooks": "^3.4.2",
"@types/jest": "<27.0.0",
"axios-mock-adapter": "^1.1.7",
"babel-jest": "^26.3.0",
"argv-parse": "^1.0.1",
"babel-eslint": "^10.0.0",
"babel-loader": "^8.0.0",
"buffer": "^5.7.1",
"compression-webpack-plugin": "^10.0.0",
"cross-env": "^5.2.0",
"cheerio": "1.0.0-rc.10",
"css-loader": "^6.8.1",
"dotenv": "^5.0.0",
"eslint": "^6.7.2",
"eslint-plugin-spellcheck": "0.0.17",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"enzyme-to-json": "^3.4.3",
"identity-obj-proxy": "^3.0.0",
"jest": "^26.4.0",
"jest-svg-transformer": "^1.0.0",
"jest-prop-type-error": "^1.1.0",
"graphql": "^15.5.0",
"jest-transform-graphql": "^2.1.0",
"path-browserify": "^1.0.1",
"prettier": "^1.19.1",
"pretty-format": "26.6.2",
"react-dnd-test-backend": "^9.4.0",
"react-redux-test-utils": "^0.2.0",
"react-test-renderer": "^17.0.1",
"redux-mock-store": "^1.2.2",
"sass": "~1.60.0",
"sass-loader": "^13.3.2",
Expand All @@ -60,6 +77,7 @@
"tabbable": "~5.2.0",
"victory-core": "~36.8.6",
"victory-pie": "~36.8.6",
"victory-legend": "~36.8.6",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^5.0.1",
Expand Down
135 changes: 135 additions & 0 deletions script/npm_test_plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/env node
/* eslint-disable import/no-dynamic-require */
/* eslint-disable no-console */
/* eslint-disable no-var */

/* This script is used to run tests for all plugins that have a `lint` script defined in their package.json
To run tests for an individual plugin, pass the plugin name as the first argument to the script
For example, to run tests for the `foreman-tasks` plugin, run: `npm run test-plugin foreman-tasks`
To pass arguments to jest, pass them after the plugin name like so: `npm run test-plugin foreman-tasks -- --debug`
*/

var fs = require('fs');
var path = require('path');
var lodash = require('lodash');
var childProcess = require('child_process');
var { packageJsonDirs } = require('./plugin_webpack_directories');

const passedArgs = process.argv.slice(2);
const coreConfigPath = path.resolve(__dirname, '../webpack/jest.config.js');
const coreConfig = require(coreConfigPath);

function runChildProcess(args, pluginPath) {
return new Promise((resolve, reject) => {
const child = childProcess.spawn('npx', args, {
shell: true,
});
// this is needed to make sure the output is not cut
let stdoutBuffer = '';
child.stdout.on('data', data => {
stdoutBuffer += data.toString();
const lines = stdoutBuffer.split('\n');
stdoutBuffer = lines.pop();
});

let stderrBuffer = `${pluginPath}: \n`;
child.stderr.on('data', data => {
stderrBuffer += data.toString();
const lines = stderrBuffer.split('\n');
stderrBuffer = lines.pop();
lines.forEach(line => console.error(line));
});
child.on('close', code => {
if (stdoutBuffer) console.log(stdoutBuffer);
if (stderrBuffer) console.error(stderrBuffer);
if (code === 0) {
resolve();
} else {
reject(new Error(`Child process exited with code ${code}`));
}
});
});
}
const runTests = async () => {
function pluginDefinesLint(pluginPath) {
var packageHasNodeModules = fs.existsSync(`${pluginPath}/node_modules`); // skip gems
var packageData = JSON.parse(fs.readFileSync(`${pluginPath}/package.json`));

return (
packageHasNodeModules && packageData.scripts && packageData.scripts.lint
);
}
var dirs = packageJsonDirs();
if (passedArgs[0] && passedArgs[0][0] !== '-') {
dirs = dirs.filter(dir => dir.endsWith(passedArgs[0]));
passedArgs.shift();
}
for (const pluginPath of dirs) {
if (pluginDefinesLint(pluginPath)) {
const testSetupFiles = [
path.resolve(__dirname, '../webpack/global_test_setup.js'),
];
const testSetupPath = path.join(pluginPath, 'webpack', 'test_setup.js');
if (fs.existsSync(testSetupPath)) {
testSetupFiles.unshift(testSetupPath);
}
const pluginConfigPath = path.join(pluginPath, 'jest.config.js');
const combinedConfigPath = path.join(
pluginPath,
'combined.jest.config.js'
);

if (fs.existsSync(pluginConfigPath)) {
// eslint-disable-next-line global-require
const pluginConfig = require(pluginConfigPath);
function customizer(objValue, srcValue) {
if (lodash.isArray(objValue)) {
return lodash.uniq(objValue.concat(srcValue));
}
}

const combinedConfig = lodash.mergeWith(
pluginConfig,
{
...coreConfig,
setupFilesAfterEnv: [
path.resolve(__dirname, '../webpack/global_test_setup.js'),
],
},
customizer
);
combinedConfig.snapshotSerializers = coreConfig.snapshotSerializers;
fs.writeFileSync(
combinedConfigPath,
`module.exports = ${JSON.stringify(combinedConfig, null, 2)};`,
'utf8'
);
}
const pluginConfigOverride = fs.existsSync(pluginConfigPath);
const configPath = pluginConfigOverride
? combinedConfigPath
: coreConfigPath;
const corePath = path.resolve(__dirname, '../');
const args = [
'jest',
`${pluginPath}/webpack`,
'--roots',
pluginPath,
corePath,
`--config=${configPath}`,
pluginConfigOverride
? ''
: `--setupFilesAfterEnv ${testSetupFiles.join(' ')}`,
'--color',
...passedArgs,
];

await runChildProcess(args, pluginPath); // Run every plugin test in a separate process
if(fs.existsSync(combinedConfigPath)) {
fs.unlinkSync(combinedConfigPath);
}
}
}
};

runTests();
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ import * as actions from '../TemplateGeneratorActions';
jest.mock('file-saver');
jest.mock('../../../redux/API');

beforeEach(() => {
API.post.mockImplementation(async () => scheduleResponse);
API.get.mockImplementation(async () => noContentResponse);
});

describe('TemplateGeneratorActions', () => {
beforeAll(() => {
jest.useFakeTimers();
});
beforeEach(() => {
API.post.mockClear();
API.get.mockClear();

API.post.mockImplementation(async () => scheduleResponse);
API.get.mockImplementation(async () => noContentResponse);
});

describe('generateTemplate', () => {
Expand All @@ -55,7 +56,6 @@ describe('TemplateGeneratorActions', () => {
API.get
.mockImplementationOnce(async () => noContentResponse)
.mockImplementationOnce(async () => generatedReportResponse);

runActionInDepth(() => actions.generateTemplate(), 3).then(callTree => {
const successAction = callTree[1][1][1];
expect(successAction).toHaveProperty('type', TEMPLATE_GENERATE_SUCCESS);
Expand Down
7 changes: 7 additions & 0 deletions webpack/test_setup.js → webpack/core_test_setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ ace.config.set('themePath', '');

jest.mock('jed');
jest.mock('./assets/javascripts/react_app/Root/Context/ForemanContext', () => ({
getForemanContext: () => ({
context: { metadata: { version: 'mocked_version' } },
}),
useForemanContext: () => ({ metadata: { version: 'mocked_version' } }),
useForemanSetContext: () => {},
useForemanVersion: () => 'mocked_version',
useForemanSettings: () => ({ perPage: 5 }),
useForemanDocUrl: () => '/url',
useForemanLocation: () => ({ title: 'location' }),
useForemanOrganization: () => ({ title: 'organization' }),
useForemanUser: () => ({ login: 'user' }),
getHostsPageUrl: displayNewHostsPage =>
displayNewHostsPage ? '/new/hosts' : '/hosts',
useForemanHostsPageUrl: () => '/hosts',
}));
jest.mock('./assets/javascripts/react_app/common/I18n');
jest.mock('./assets/javascripts/foreman_tools', () => ({
Expand Down
19 changes: 19 additions & 0 deletions webpack/global_test_setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// eslint-disable-next-line import/no-unresolved, import/extensions
import 'core-js/shim';
// eslint-disable-next-line import/no-extraneous-dependencies
import 'regenerator-runtime/runtime';

const { configure } = require('./theforeman-test');
const Adapter = require('enzyme-adapter-react-16');

configure({ adapter: new Adapter() });

// https://github.com/facebook/jest/issues/6121
// eslint-disable-next-line no-console
const { error } = console;
// eslint-disable-next-line no-console
console.error = (message, ...args) => {
error.apply(console, args); // keep default behaviour
const err = message instanceof Error ? message : new Error(message);
throw err;
};
71 changes: 71 additions & 0 deletions webpack/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* eslint-disable spellcheck/spell-checker */
const fs = require('fs');
const path = require('path');

const nodeModules = path.resolve(__dirname, '..', 'node_modules');
const packageJsonPath = path.resolve(__dirname, '..', 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const vendorCorePackageJsonPath = path.resolve(nodeModules, '@theforeman/vendor-core', 'package.json');
const vendorCorePackageJson = JSON.parse(fs.readFileSync(vendorCorePackageJsonPath, 'utf8'));
const dependencies = {
...packageJson.dependencies,
...packageJson.devDependencies,
...vendorCorePackageJson.dependencies,
'@apollo/client/testing': '@apollo/client/testing',
}; // Use shared dependencies from foreman node_modules and not plugin node_modules to avoid jest errors due to multiple instances of same package

const moduleNameMapper = {};
Object.keys(dependencies).forEach(dep => {
moduleNameMapper[`^${dep}$`] = path.resolve(nodeModules, dep);
});

const foremanReactFull = path.resolve(
__dirname,
'assets/javascripts/react_app'
);
const foremanTest = path.resolve(__dirname, 'theforeman-test.js');

module.exports = {
verbose: true,
logHeapUsage: true,
maxWorkers: 2,
collectCoverage: true,
coverageReporters: ['lcov'],
coverageDirectory: `../coverage`,
setupFiles: [require.resolve('jest-prop-type-error')],
testPathIgnorePatterns: [
'/node_modules/',
'<rootDir>/foreman/',
'<rootDir>/.+fixtures.+',
'foreman/webpack', // dont test foreman core in plugins
],
testMatch: ['**/*.test.js'],
moduleDirectories: [
`node_modules`,
`<rootDir>/node_modules/@theforeman/vendor-core/node_modules`,
`node_modules/@theforeman/vendor-core/node_modules`,
'<rootDir>/node_modules',
],
transform: {
'^.+\\.js?$': 'babel-jest',
'\\.(gql|graphql)$': require.resolve('jest-transform-graphql'), // for graphql-tag
},
snapshotSerializers: [require.resolve('enzyme-to-json/serializer')],
moduleNameMapper: {
'^.+\\.(png|gif|css|scss)$': 'identity-obj-proxy',
...moduleNameMapper,
'^dnd-core$': `${nodeModules}/dnd-core/dist/cjs`,
'^react-dnd$': `${nodeModules}/react-dnd/dist/cjs`,
'^react-dnd-html5-backend$': `${nodeModules}/react-dnd-html5-backend/dist/cjs`,
'^react-dnd-touch-backend$': `${nodeModules}/react-dnd-touch-backend/dist/cjs`,
'^react-dnd-test-backend$': `${nodeModules}/react-dnd-test-backend/dist/cjs`,
'^react-dnd-test-utils$': `${nodeModules}/react-dnd-test-utils/dist/cjs`,
'^foremanReact(.*)$': `${foremanReactFull}/$1`,
'^@theforeman/test$': foremanTest,
'^victory(.*)$': `${nodeModules}/victory$1`,
},
globals: {
__testing__: true,
URL_PREFIX: '',
},
};
Loading

0 comments on commit 1c46799

Please sign in to comment.