semantic-release/test/plugins/plugins.test.js

325 lines
11 KiB
JavaScript

import path from "path";
import test from "ava";
import { copy, outputFile } from "fs-extra";
import { stub } from "sinon";
import { temporaryDirectory } from "tempy";
import getPlugins from "../../lib/plugins/index.js";
// Save the current working directory
const cwd = process.cwd();
test.beforeEach((t) => {
// Stub the logger functions
t.context.log = stub();
t.context.success = stub();
t.context.logger = { log: t.context.log, success: t.context.success, scope: () => t.context.logger };
});
test("Export default plugins", async (t) => {
const plugins = await getPlugins({ cwd, options: {}, logger: t.context.logger }, {});
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test("Export plugins based on steps config", async (t) => {
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: {
verifyConditions: ["./test/fixtures/plugin-noop.cjs", { path: "./test/fixtures/plugin-noop.cjs" }],
generateNotes: "./test/fixtures/plugin-noop.cjs",
analyzeCommits: { path: "./test/fixtures/plugin-noop.cjs" },
verifyRelease: () => {},
},
},
{}
);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test('Export plugins based on "plugins" config (array)', async (t) => {
const plugin1 = { verifyConditions: stub(), publish: stub() };
const plugin2 = { verifyConditions: stub(), verifyRelease: stub() };
const plugins = await getPlugins(
{ cwd, logger: t.context.logger, options: { plugins: [plugin1, [plugin2, {}]], verifyRelease: () => {} } },
{}
);
await plugins.verifyConditions({ options: {} });
t.true(plugin1.verifyConditions.calledOnce);
t.true(plugin2.verifyConditions.calledOnce);
await plugins.publish({ options: {} });
t.true(plugin1.publish.calledOnce);
await plugins.verifyRelease({ options: {} });
t.true(plugin2.verifyRelease.notCalled);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test('Export plugins based on "plugins" config (single definition)', async (t) => {
const plugin1 = { verifyConditions: stub(), publish: stub() };
const plugins = await getPlugins({ cwd, logger: t.context.logger, options: { plugins: plugin1 } }, {});
await plugins.verifyConditions({ options: {} });
t.true(plugin1.verifyConditions.calledOnce);
await plugins.publish({ options: {} });
t.true(plugin1.publish.calledOnce);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test('Merge global options, "plugins" options and step options', async (t) => {
const plugin1 = [{ verifyConditions: stub(), publish: stub() }, { pluginOpt1: "plugin1" }];
const plugin2 = [{ verifyConditions: stub() }, { pluginOpt2: "plugin2" }];
const plugin3 = [stub(), { pluginOpt3: "plugin3" }];
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: { globalOpt: "global", plugins: [plugin1, plugin2], verifyRelease: [plugin3] },
},
{}
);
await plugins.verifyConditions({ options: {} });
t.deepEqual(plugin1[0].verifyConditions.args[0][0], { globalOpt: "global", pluginOpt1: "plugin1" });
t.deepEqual(plugin2[0].verifyConditions.args[0][0], { globalOpt: "global", pluginOpt2: "plugin2" });
await plugins.publish({ options: {} });
t.deepEqual(plugin1[0].publish.args[0][0], { globalOpt: "global", pluginOpt1: "plugin1" });
await plugins.verifyRelease({ options: {} });
t.deepEqual(plugin3[0].args[0][0], { globalOpt: "global", pluginOpt3: "plugin3" });
});
test('Unknown steps of plugins configured in "plugins" are ignored', async (t) => {
const plugin1 = { verifyConditions: () => {}, unknown: () => {} };
const plugins = await getPlugins({ cwd, logger: t.context.logger, options: { plugins: [plugin1] } }, {});
t.is(typeof plugins.verifyConditions, "function");
t.is(plugins.unknown, undefined);
});
test("Export plugins loaded from the dependency of a shareable config module", async (t) => {
const cwd = temporaryDirectory();
await copy(
"./test/fixtures/plugin-noop.cjs",
path.resolve(cwd, "node_modules/shareable-config/node_modules/custom-plugin/index.js")
);
await outputFile(path.resolve(cwd, "node_modules/shareable-config/index.js"), "");
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: {
verifyConditions: ["custom-plugin", { path: "custom-plugin" }],
generateNotes: "custom-plugin",
analyzeCommits: { path: "custom-plugin" },
verifyRelease: () => {},
},
},
{ "custom-plugin": "shareable-config" }
);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test("Export plugins loaded from the dependency of a shareable config file", async (t) => {
const cwd = temporaryDirectory();
await copy("./test/fixtures/plugin-noop.cjs", path.resolve(cwd, "plugin/plugin-noop.cjs"));
await outputFile(path.resolve(cwd, "shareable-config.js"), "");
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: {
verifyConditions: ["./plugin/plugin-noop.cjs", { path: "./plugin/plugin-noop.cjs" }],
generateNotes: "./plugin/plugin-noop.cjs",
analyzeCommits: { path: "./plugin/plugin-noop.cjs" },
verifyRelease: () => {},
},
},
{ "./plugin/plugin-noop": "./shareable-config.js" }
);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, "function");
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.verifyRelease, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.prepare, "function");
t.is(typeof plugins.publish, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
});
test("Use default when only options are passed for a single plugin", async (t) => {
const analyzeCommits = {};
const generateNotes = {};
const publish = {};
const success = () => {};
const fail = [() => {}];
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: {
plugins: ["@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator"],
analyzeCommits,
generateNotes,
publish,
success,
fail,
},
},
{}
);
// Verify the module returns a function for each plugin
t.is(typeof plugins.analyzeCommits, "function");
t.is(typeof plugins.generateNotes, "function");
t.is(typeof plugins.success, "function");
t.is(typeof plugins.fail, "function");
// Verify only the plugins defined as an object with no `path` are set to the default value
t.falsy(success.path);
t.falsy(fail.path);
});
test("Merge global options with plugin options", async (t) => {
const plugins = await getPlugins(
{
cwd,
logger: t.context.logger,
options: {
globalOpt: "global",
otherOpt: "globally-defined",
verifyRelease: { path: "./test/fixtures/plugin-result-config", localOpt: "local", otherOpt: "locally-defined" },
},
},
{}
);
const [result] = await plugins.verifyRelease({ options: {} });
t.deepEqual(result.pluginConfig, { localOpt: "local", globalOpt: "global", otherOpt: "locally-defined" });
});
test("Throw an error for each invalid plugin configuration", async (t) => {
const errors = [
...(
await t.throwsAsync(() =>
getPlugins(
{
cwd,
logger: t.context.logger,
options: {
plugins: ["@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator"],
verifyConditions: 1,
analyzeCommits: [],
verifyRelease: [{}],
generateNotes: [{ path: null }],
},
},
{}
)
)
).errors,
];
t.is(errors[0].name, "SemanticReleaseError");
t.is(errors[0].code, "EPLUGINCONF");
t.is(errors[1].name, "SemanticReleaseError");
t.is(errors[1].code, "EPLUGINCONF");
t.is(errors[2].name, "SemanticReleaseError");
t.is(errors[2].code, "EPLUGINCONF");
t.is(errors[3].name, "SemanticReleaseError");
t.is(errors[3].code, "EPLUGINCONF");
});
test('Throw EPLUGINSCONF error if the "plugins" option contains an old plugin definition (returns a function)', async (t) => {
const errors = [
...(
await t.throwsAsync(() =>
getPlugins(
{
cwd,
logger: t.context.logger,
options: { plugins: ["./test/fixtures/multi-plugin.cjs", "./test/fixtures/plugin-noop.cjs", () => {}] },
},
{}
)
)
).errors,
];
t.is(errors[0].name, "SemanticReleaseError");
t.is(errors[0].code, "EPLUGINSCONF");
t.is(errors[1].name, "SemanticReleaseError");
t.is(errors[1].code, "EPLUGINSCONF");
});
test('Throw EPLUGINSCONF error for each invalid definition if the "plugins" option', async (t) => {
const errors = [
...(
await t.throwsAsync(() =>
getPlugins({ cwd, logger: t.context.logger, options: { plugins: [1, { path: 1 }, [() => {}, {}, {}]] } }, {})
)
).errors,
];
t.is(errors[0].name, "SemanticReleaseError");
t.is(errors[0].code, "EPLUGINSCONF");
t.is(errors[1].name, "SemanticReleaseError");
t.is(errors[1].code, "EPLUGINSCONF");
t.is(errors[2].name, "SemanticReleaseError");
t.is(errors[2].code, "EPLUGINSCONF");
});