2021-12-03 15:32:58 +01:00

272 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { createRequire, Module } from "module";
import path from "path";
import fs from "fs";
import child from "child_process";
import { fileURLToPath } from "url";
const dirname = path.dirname(fileURLToPath(import.meta.url));
const require = createRequire(import.meta.url);
const registerFile = require.resolve("../lib/index");
const testCacheFilename = path.join(dirname, ".index.babel");
const testFile = require.resolve("./fixtures/babelrc/es2015");
const testFileContent = fs.readFileSync(testFile);
const piratesPath = require.resolve("pirates");
const smsPath = require.resolve("source-map-support");
const defaultOptions = {
exts: [".js", ".jsx", ".es6", ".es", ".mjs", ".cjs"],
ignoreNodeModules: false,
};
function cleanCache() {
try {
fs.unlinkSync(testCacheFilename);
} catch (e) {
// It is convenient to always try to clear
}
}
function resetCache() {
process.env.BABEL_CACHE_PATH = null;
}
const OLD_JEST_MOCKS = !!jest.doMock;
describe("@babel/register", function () {
let currentHook, currentOptions, sourceMapSupport;
const mocks = {
["pirates"]: {
addHook(hook, opts) {
currentHook = hook;
currentOptions = opts;
return () => {
currentHook = null;
currentOptions = null;
};
},
},
["source-map-support"]: {
install() {
sourceMapSupport = true;
},
},
};
let babelRegister;
function setupRegister(config = { babelrc: false }) {
process.env.BABEL_CACHE_PATH = testCacheFilename;
config = {
cwd: path.dirname(testFile),
...config,
};
babelRegister = require(registerFile);
babelRegister.default(config);
}
function revertRegister() {
if (babelRegister) {
babelRegister.revert();
delete require.cache[registerFile];
babelRegister = null;
}
cleanCache();
}
beforeEach(() => {
currentHook = null;
currentOptions = null;
sourceMapSupport = false;
});
afterEach(async () => {
// @babel/register saves the cache on process.nextTick.
// We need to wait for at least one tick so that when jest
// tears down the testing environment @babel/register has
// already finished.
await new Promise(setImmediate);
revertRegister();
});
afterAll(() => {
resetCache();
});
if (OLD_JEST_MOCKS) {
jest.doMock("pirates", () => mocks["pirates"]);
jest.doMock("source-map-support", () => mocks["source-map-support"]);
afterEach(() => {
jest.resetModules();
});
} else {
let originalRequireCacheDescriptor;
beforeAll(() => {
originalRequireCacheDescriptor = Object.getOwnPropertyDescriptor(
Module,
"_cache",
);
});
beforeEach(() => {
const isEmptyObj = obj =>
Object.getPrototypeOf(obj) === null && Object.keys(obj).length === 0;
// This setter intercepts the Module._cache assignment in
// packages/babel-register/src/nodeWrapper.js to install in the
// internal isolated cache.
const emptyInitialCache = {};
Object.defineProperty(Module, "_cache", {
get: () => emptyInitialCache,
set(value) {
expect(isEmptyObj(value)).toBe(true);
Object.defineProperty(Module, "_cache", {
value,
enumerable: originalRequireCacheDescriptor.enumerable,
configurable: originalRequireCacheDescriptor.configurable,
writable: originalRequireCacheDescriptor.writable,
});
value[piratesPath] = { exports: mocks["pirates"] };
value[smsPath] = { exports: mocks["source-map-support"] };
},
enumerable: originalRequireCacheDescriptor.enumerable,
configurable: originalRequireCacheDescriptor.configurable,
});
});
afterAll(() => {
Object.defineProperty(Module, "_cache", originalRequireCacheDescriptor);
});
}
test("registers hook correctly", () => {
setupRegister();
expect(typeof currentHook).toBe("function");
expect(currentOptions).toEqual(defaultOptions);
});
test("unregisters hook correctly", () => {
setupRegister();
revertRegister();
expect(currentHook).toBeNull();
expect(currentOptions).toBeNull();
});
test("installs source map support by default", () => {
setupRegister();
currentHook("const a = 1;", testFile);
expect(sourceMapSupport).toBe(true);
});
test("installs source map support when requested", () => {
setupRegister({
babelrc: false,
sourceMaps: true,
});
currentHook("const a = 1;", testFile);
expect(sourceMapSupport).toBe(true);
});
test("does not install source map support if asked not to", () => {
setupRegister({
babelrc: false,
sourceMaps: false,
});
currentHook("const a = 1;", testFile);
expect(sourceMapSupport).toBe(false);
});
it("returns concatenatable sourceRoot and sources", async () => {
// The Source Maps R3 standard https://sourcemaps.info/spec.html states
// that `sourceRoot` is “prepended to the individual entries in the
// source field.” If `sources` contains file names, and `sourceRoot`
// is intended to refer to a directory but doesnt end with a trailing
// slash, any consumers of the source map are in for a bad day.
//
// The underlying problem seems to only get triggered if one file
// requires() another with @babel/register active, and I couldnt get
// that working inside a test, possibly because of jests mocking
// hooks, so we spawn a separate process.
const output = await spawnNodeAsync([
"-r",
registerFile,
require.resolve("./fixtures/source-map/index"),
]);
const sourceMap = JSON.parse(output);
expect(sourceMap.map.sourceRoot + sourceMap.map.sources[0]).toBe(
require.resolve("./fixtures/source-map/foo/bar"),
);
});
test("hook transpiles with config", () => {
setupRegister({
babelrc: false,
sourceMaps: false,
plugins: ["@babel/transform-modules-commonjs"],
});
const result = currentHook(testFileContent, testFile);
expect(result).toBe('"use strict";\n\nrequire("assert");');
});
test("hook transpiles with babelrc", () => {
setupRegister({
babelrc: true,
sourceMaps: false,
});
const result = currentHook(testFileContent, testFile);
expect(result).toBe('"use strict";\n\nrequire("assert");');
});
test("transforms modules used within register", async () => {
// Need a clean environment without `convert-source-map`
// already in the require cache, so we spawn a separate process
const output = await spawnNodeAsync([
require.resolve("./fixtures/internal-modules/index.js"),
]);
const { convertSourceMap } = JSON.parse(output);
expect(convertSourceMap).toMatch("/* transformed */");
});
});
function spawnNodeAsync(args) {
const spawn = child.spawn(process.execPath, args, { cwd: dirname });
let output = "";
let callback;
for (const stream of [spawn.stderr, spawn.stdout]) {
stream.on("data", chunk => {
output += chunk;
});
}
spawn.on("close", function () {
callback(output);
});
return new Promise(resolve => {
callback = resolve;
});
}