Files

552 lines
16 KiB
JavaScript

import {
loadOptions,
loadOptionsSync,
loadOptionsAsync,
loadPartialConfig,
loadPartialConfigAsync,
loadPartialConfigSync,
createConfigItem,
createConfigItemSync,
} from "../lib/index.js";
import path from "node:path";
import { itNoWin32, commonJS } from "$repo-utils";
const { require, __dirname } = commonJS(import.meta.url);
describe("@babel/core config loading", () => {
const FILEPATH = path.join(
__dirname,
"fixtures",
"config-loading",
"folder",
"example.js",
);
afterEach(() => {
delete process.env.INVALIDATE_BABELRC;
delete process.env.INVALIDATE_PRESET1;
delete process.env.INVALIDATE_PRESET2;
delete process.env.INVALIDATE_PRESET3;
delete process.env.INVALIDATE_PLUGIN1;
delete process.env.INVALIDATE_PLUGIN2;
delete process.env.INVALIDATE_PLUGIN3;
delete process.env.INVALIDATE_PLUGIN4;
delete process.env.INVALIDATE_PLUGIN5;
delete process.env.INVALIDATE_PLUGIN6;
});
function makeOpts(skipProgrammatic = false) {
return {
cwd: path.dirname(FILEPATH),
filename: FILEPATH,
presets: skipProgrammatic
? null
: [[require("./fixtures/config-loading/preset3.js"), {}]],
plugins: skipProgrammatic
? null
: [[require("./fixtures/config-loading/plugin6.js"), {}]],
};
}
describe("createConfigItem", () => {
it("throws on undefined callback", () => {
function myPlugin() {
return {};
}
expect(() =>
createConfigItem(myPlugin),
).toThrowErrorMatchingInlineSnapshot(
`"Starting from Babel 8.0.0, the 'createConfigItem' function expects a callback. If you need to call it synchronously, please use 'createConfigItemSync'."`,
);
});
});
describe("createConfigItemSync", () => {
// Windows uses different file paths
itNoWin32("can be called synchronously with one param", () => {
function myPlugin() {
return {};
}
expect(createConfigItemSync(myPlugin)).toEqual({
dirname: process.cwd(),
file: undefined,
name: undefined,
options: undefined,
value: myPlugin,
});
});
itNoWin32("can be called synchronously with two params", () => {
function myPlugin() {
return {};
}
expect(
createConfigItemSync(myPlugin, { dirname: "/foo", type: "plugin" }),
).toEqual({
dirname: "/foo",
file: undefined,
name: undefined,
options: undefined,
value: myPlugin,
});
});
});
describe("loadPartialConfig", () => {
it("throws on undefined callback", () => {
expect(() =>
loadPartialConfig({
...makeOpts(true),
babelrc: false,
configFile: false,
}),
).toThrowErrorMatchingInlineSnapshot(
`"Starting from Babel 8.0.0, the 'loadPartialConfig' function expects a callback. If you need to call it synchronously, please use 'loadPartialConfigSync'."`,
);
});
});
describe("loadPartialConfigSync", () => {
it("should preserve disabled plugins in the partial config", () => {
const plugin = function () {
return {};
};
const opts = loadPartialConfigSync({
...makeOpts(true),
babelrc: false,
configFile: false,
plugins: [[plugin, false]],
});
expect(opts.options.plugins.length).toBe(1);
const item = opts.options.plugins[0];
expect(item.value).toBe(plugin);
expect(item.options).toBe(false);
});
it("should preserve disabled presets in the partial config", () => {
const preset = function () {
return {};
};
const opts = loadPartialConfigSync({
...makeOpts(true),
babelrc: false,
configFile: false,
presets: [[preset, false]],
});
expect(opts.options.presets.length).toBe(1);
const item = opts.options.presets[0];
expect(item.value).toBe(preset);
expect(item.options).toBe(false);
});
it("should always set 'rootMode' to 'root'", async () => {
const cwd = path.join(
__dirname,
"fixtures",
"config-loading",
"root",
"nested",
);
const { options } = loadPartialConfigSync({
cwd,
filename: path.join(cwd, "file.js"),
rootMode: "upward",
});
expect(options.root).toBe(path.join(cwd, ".."));
expect(options.rootMode).toBe("root");
});
});
describe("loadPartialConfigAsync", () => {
// https://github.com/babel/babel/issues/15916
it("two calls in parallel loading the same ESM config", async () => {
const cwd = path.join(
__dirname,
"fixtures",
"config",
"config-files",
"babel-config-mjs-object",
);
const [config1, config2] = await Promise.all([
loadPartialConfigAsync({
cwd,
filename: path.join(cwd, "./a.js"),
}),
loadPartialConfigAsync({
cwd,
filename: path.join(cwd, "./b.js"),
}),
]);
expect(config1.options.plugins).toEqual(config2.options.plugins);
});
});
describe("loadOptions", () => {
it("throws on undefined callback", () => {
const opts = makeOpts();
expect(() => loadOptions(opts)).toThrowErrorMatchingInlineSnapshot(
`"Starting from Babel 8.0.0, the 'loadOptions' function expects a callback. If you need to call it synchronously, please use 'loadOptionsSync'."`,
);
});
});
describe("loadOptionsSync", () => {
it("should load and cache the config with plugins and presets", () => {
const opts = makeOpts();
const options1 = loadOptionsSync(opts);
expect(options1.plugins.map(p => p.key)).toEqual([
"plugin1",
"plugin2",
"plugin6",
"plugin5",
"plugin4",
"plugin3",
]);
const options2 = loadOptionsSync(opts);
expect(options2.plugins.length).toBe(options1.plugins.length);
for (let i = 0; i < options2.plugins.length; i++) {
expect(options2.plugins[i]).toBe(options1.plugins[i]);
}
});
it("should load and cache the config for unique opts objects", () => {
const options1 = loadOptionsSync(makeOpts(true));
expect(options1.plugins.map(p => p.key)).toEqual([
"plugin1",
"plugin2",
"plugin4",
"plugin3",
]);
const options2 = loadOptionsSync(makeOpts(true));
expect(options2.plugins.length).toBe(options1.plugins.length);
for (let i = 0; i < options2.plugins.length; i++) {
expect(options2.plugins[i]).toBe(options1.plugins[i]);
}
});
it("should invalidate config file plugins", () => {
const opts = makeOpts();
const options1 = loadOptionsSync(opts);
process.env.INVALIDATE_PLUGIN1 = true;
const options2 = loadOptionsSync(opts);
expect(options2.plugins.length).toBe(options1.plugins.length);
expect(options2.plugins[0]).not.toBe(options1.plugins[0]);
for (let i = 1; i < options1.plugins.length; i++) {
expect(options2.plugins[i]).toBe(options1.plugins[i]);
}
process.env.INVALIDATE_PLUGIN3 = true;
const options3 = loadOptionsSync(opts);
expect(options3.plugins.length).toBe(options1.plugins.length);
expect(options3.plugins.length).toBe(6);
expect(options3.plugins[0]).not.toBe(options1.plugins[0]);
expect(options3.plugins[5]).not.toBe(options1.plugins[5]);
for (let i = 1; i < 5; i++) {
expect(options3.plugins[i]).toBe(options1.plugins[i]);
}
});
it("should invalidate config file presets and their children", () => {
const opts = makeOpts();
const options1 = loadOptionsSync(opts);
process.env.INVALIDATE_PRESET1 = true;
const options2 = loadOptionsSync(opts);
expect(options2.plugins.length).toBe(options1.plugins.length);
expect(options2.plugins.length).toBe(6);
expect(options2.plugins[5]).not.toBe(options1.plugins[5]);
for (let i = 0; i < 5; i++) {
expect(options2.plugins[i]).toBe(options1.plugins[i]);
}
process.env.INVALIDATE_PRESET2 = true;
const options3 = loadOptionsSync(opts);
expect(options3.plugins.length).toBe(options1.plugins.length);
expect(options3.plugins.length).toBe(6);
expect(options3.plugins[4]).not.toBe(options1.plugins[4]);
expect(options3.plugins[5]).not.toBe(options1.plugins[5]);
for (let i = 0; i < 4; i++) {
expect(options3.plugins[i]).toBe(options1.plugins[i]);
}
});
it("should invalidate the config file and its plugins/presets", () => {
const opts = makeOpts();
const options1 = loadOptionsSync(opts);
process.env.INVALIDATE_BABELRC = true;
const options2 = loadOptionsSync(opts);
expect(options2.plugins.length).toBe(options1.plugins.length);
expect(options2.plugins.length).toBe(6);
expect(options2.plugins[0]).not.toBe(options1.plugins[0]);
expect(options2.plugins[1]).not.toBe(options1.plugins[1]);
expect(options2.plugins[4]).not.toBe(options1.plugins[4]);
expect(options2.plugins[5]).not.toBe(options1.plugins[5]);
expect(options2.plugins[2]).toBe(options1.plugins[2]);
expect(options2.plugins[3]).toBe(options1.plugins[3]);
});
});
describe("loadOptionsAsync", () => {
it("should load and cache the config with plugins and presets when executed in parallel", async () => {
const opts = makeOpts();
const [options1, options2] = await Promise.all([
loadOptionsAsync(opts),
loadOptionsAsync(opts),
]);
expect(options1.plugins.map(p => p.key)).toEqual([
"plugin1",
"plugin2",
"plugin6",
"plugin5",
"plugin4",
"plugin3",
]);
expect(options2.plugins.length).toBe(options1.plugins.length);
for (let i = 0; i < options2.plugins.length; i++) {
expect(options2.plugins[i]).toBe(options1.plugins[i]);
}
});
});
describe("programmatic plugins/presets", () => {
it("should not invalidate the plugins when given a fresh object", () => {
const opts = makeOpts();
const options1 = loadOptionsSync(opts);
const options2 = loadOptionsSync(Object.assign({}, opts));
expect(options2.plugins.length).toBe(options1.plugins.length);
for (let i = 0; i < options2.plugins.length; i++) {
expect(options2.plugins[i]).toBe(options1.plugins[i]);
}
});
it("should not invalidate the plugins when given a fresh arrays", () => {
const opts = makeOpts();
const options1 = loadOptionsSync(opts);
const options2 = loadOptionsSync({
...opts,
plugins: opts.plugins.slice(),
});
expect(options2.plugins.length).toBe(options1.plugins.length);
for (let i = 0; i < options2.plugins.length; i++) {
expect(options2.plugins[i]).toBe(options1.plugins[i]);
}
});
it("should not invalidate the presets when given a fresh arrays", () => {
const opts = makeOpts();
const options1 = loadOptionsSync(opts);
const options2 = loadOptionsSync({
...opts,
presets: opts.presets.slice(),
});
expect(options2.plugins.length).toBe(options1.plugins.length);
for (let i = 0; i < options2.plugins.length; i++) {
expect(options2.plugins[i]).toBe(options1.plugins[i]);
}
});
it("should invalidate the plugins when given a fresh options", () => {
const opts = makeOpts();
const options1 = loadOptionsSync(opts);
const options2 = loadOptionsSync({
...opts,
plugins: opts.plugins.map(([plg, opt]) => [plg, { ...opt }]),
});
expect(options2.plugins.length).toBe(options1.plugins.length);
expect(options2.plugins.length).toBe(6);
expect(options2.plugins[2]).not.toBe(options1.plugins[2]);
expect(options2.plugins[0]).toBe(options1.plugins[0]);
expect(options2.plugins[1]).toBe(options1.plugins[1]);
expect(options2.plugins[3]).toBe(options1.plugins[3]);
expect(options2.plugins[4]).toBe(options1.plugins[4]);
expect(options2.plugins[5]).toBe(options1.plugins[5]);
});
it("should invalidate the presets when given a fresh options", () => {
const opts = makeOpts();
const options1 = loadOptionsSync(opts);
const options2 = loadOptionsSync({
...opts,
presets: opts.presets.map(([plg, opt]) => [plg, { ...opt }]),
});
expect(options2.plugins.length).toBe(options1.plugins.length);
expect(options2.plugins.length).toBe(6);
expect(options2.plugins[3]).not.toBe(options1.plugins[3]);
expect(options2.plugins[0]).toBe(options1.plugins[0]);
expect(options2.plugins[1]).toBe(options1.plugins[1]);
expect(options2.plugins[2]).toBe(options1.plugins[2]);
expect(options2.plugins[4]).toBe(options1.plugins[4]);
expect(options2.plugins[5]).toBe(options1.plugins[5]);
});
it("should invalidate the programmatic plugins", () => {
const opts = makeOpts();
const options1 = loadOptionsSync(opts);
process.env.INVALIDATE_PLUGIN6 = true;
const options2 = loadOptionsSync(opts);
expect(options2.plugins.length).toBe(options1.plugins.length);
expect(options2.plugins.length).toBe(6);
expect(options2.plugins[2]).not.toBe(options1.plugins[2]);
expect(options2.plugins[0]).toBe(options1.plugins[0]);
expect(options2.plugins[1]).toBe(options1.plugins[1]);
expect(options2.plugins[3]).toBe(options1.plugins[3]);
expect(options2.plugins[4]).toBe(options1.plugins[4]);
expect(options2.plugins[5]).toBe(options1.plugins[5]);
});
it("should invalidate the programmatic presets and their children", () => {
const opts = makeOpts();
const options1 = loadOptionsSync(opts);
process.env.INVALIDATE_PRESET3 = true;
const options2 = loadOptionsSync(opts);
expect(options2.plugins.length).toBe(options1.plugins.length);
expect(options2.plugins.length).toBe(6);
expect(options2.plugins[3]).not.toBe(options1.plugins[3]);
expect(options2.plugins[0]).toBe(options1.plugins[0]);
expect(options2.plugins[1]).toBe(options1.plugins[1]);
expect(options2.plugins[2]).toBe(options1.plugins[2]);
expect(options2.plugins[4]).toBe(options1.plugins[4]);
expect(options2.plugins[5]).toBe(options1.plugins[5]);
});
it("should thrown when plugin is not valid", () => {
const fooPlugin = {
inherits: "inherits-should-not-be-string",
};
const opts = {
cwd: path.dirname(FILEPATH),
filename: FILEPATH,
plugins: [fooPlugin],
};
expect(() => loadOptionsSync(opts)).toThrow(
/\.inherits must be a function, or undefined/,
);
});
it("should throw when plugin contains `enter` handler", () => {
const fooPlugin = {
visitor: {
enter() {},
},
};
const opts = {
cwd: path.dirname(FILEPATH),
filename: FILEPATH,
plugins: [fooPlugin],
};
expect(() => loadOptionsSync(opts)).toThrow(
/\.visitor cannot contain catch-all "enter" or "exit" handlers\. Please target individual nodes\./,
);
});
});
describe("caller metadata", () => {
it("should pass caller data through", () => {
const options1 = loadOptionsSync({
...makeOpts(),
caller: {
name: "babel-test",
someFlag: true,
},
});
expect(options1.caller.name).toBe("babel-test");
expect(options1.caller.someFlag).toBe(true);
});
it("should pass unknown caller data through", () => {
const options1 = loadOptionsSync({
...makeOpts(),
caller: undefined,
});
expect(options1.caller).toBeUndefined();
});
it("should pass caller data to test functions", () => {
const options1 = loadOptionsSync({
...makeOpts(),
caller: {
name: "babel-test",
someFlag: true,
},
overrides: [
{
test: (filename, { caller }) => caller.name === "babel-test",
comments: false,
},
{
test: (filename, { caller }) => caller.name !== "babel-test",
ast: false,
},
],
});
expect(options1.comments).toBe(false);
expect(options1.ast).not.toBe(false);
});
});
});