Files
2026-DEV-BUT3/node_modules/resolve/test/exports_sync.js
T
2026-04-25 15:28:39 +02:00

443 lines
15 KiB
JavaScript

'use strict';
var fs = require('fs');
var path = require('path');
var test = require('tape');
var resolve = require('../sync');
var fixturesDir = path.join(__dirname, 'list-exports', 'packages', 'tests', 'fixtures');
var categories = [
'broken',
'broken-dir-slash-conditions',
'conditions',
'experimental',
'pattern-trailers',
'pattern-trailers+json-imports',
'pattern-trailers-no-dir-slash',
'pattern-trailers-no-dir-slash+json-imports',
'patterns',
'require-esm',
'strips-types',
'subpath-imports-slash'
// 'pre-exports' is tested separately since it uses main/index resolution
];
// Fixtures that are symlinks pointing outside the fixture dir cause path confusion
// ex-private is a private package whose expected files don't include exports data
var skipFixtures = ['list-exports', 'ls-exports', 'ex-private'];
function getFixtures() {
return fs.readdirSync(fixturesDir).filter(function (name) {
if (skipFixtures.indexOf(name) !== -1) {
return false;
}
var stat = fs.statSync(path.join(fixturesDir, name));
return stat.isDirectory();
});
}
function loadExpected(fixtureName, category) {
var expectedPath = path.join(fixturesDir, fixtureName, 'expected', category + '.json');
if (!fs.existsSync(expectedPath)) {
return null;
}
try {
return JSON.parse(fs.readFileSync(expectedPath, 'utf8'));
} catch (e) {
return null;
}
}
function loadProjectPkg(fixtureName) {
var pkgPath = path.join(fixturesDir, fixtureName, 'project', 'package.json');
try {
return JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
} catch (e) {
return null;
}
}
test('exports resolution - exportsCategory option', function (t) {
var fixtures = getFixtures();
fixtures.forEach(function (fixtureName) {
var projectPkg = loadProjectPkg(fixtureName);
if (!projectPkg) {
return;
}
var projectDir = path.join(fixturesDir, fixtureName, 'project');
var pkgName = projectPkg.name;
categories.forEach(function (category) {
var expected = loadExpected(fixtureName, category);
if (!expected || !expected.exports || !expected.exports[category]) {
return;
}
var requireMap = expected.exports[category].require;
if (!requireMap || typeof requireMap !== 'object') {
return;
}
Object.keys(requireMap).forEach(function (subpath) {
var expectedFile = requireMap[subpath];
var specifier = subpath === '.' ? pkgName : pkgName + subpath.substring(1);
t.test(fixtureName + ' / ' + category + ' / ' + subpath, function (st) {
st.plan(1);
try {
var result = resolve(specifier, {
basedir: __dirname,
exportsCategory: category,
extensions: ['.js', '.json'],
packageIterator: function () {
return [projectDir];
}
});
var relativeResult = './' + path.relative(projectDir, result).split(path.sep).join('/');
st.equal(relativeResult, expectedFile, specifier + ' resolves to ' + expectedFile);
} catch (e) {
st.fail('Unexpected error for ' + specifier + ': ' + e.message);
}
});
});
});
});
t.end();
});
test('exports resolution - pre-exports category uses main/index', function (t) {
var fixtures = getFixtures();
fixtures.forEach(function (fixtureName) {
var projectPkg = loadProjectPkg(fixtureName);
if (!projectPkg) {
return;
}
var projectDir = path.join(fixturesDir, fixtureName, 'project');
var pkgName = projectPkg.name;
var expected = loadExpected(fixtureName, 'pre-exports');
if (!expected || !expected.exports || !expected.exports['pre-exports']) {
return;
}
var requireMap = expected.exports['pre-exports'].require;
if (!requireMap || typeof requireMap !== 'object') {
return;
}
// For pre-exports, only test the main entry point (.)
var mainEntry = requireMap['.'];
if (!mainEntry) {
return;
}
t.test(fixtureName + ' / pre-exports / .', function (st) {
st.plan(1);
try {
var result = resolve(pkgName, {
basedir: __dirname,
exportsCategory: 'pre-exports',
extensions: ['.js', '.json'],
packageIterator: function () {
return [projectDir];
}
});
var relativeResult = './' + path.relative(projectDir, result).split(path.sep).join('/');
st.equal(relativeResult, mainEntry, pkgName + ' resolves to ' + mainEntry);
} catch (e) {
st.fail('Unexpected error for ' + pkgName + ': ' + e.message);
}
});
});
t.end();
});
test('exports resolution - mutual exclusivity of options', function (t) {
t.test('exportsCategory and engines (string) are mutually exclusive', function (st) {
st.plan(1);
try {
resolve('tape', {
basedir: __dirname,
exportsCategory: 'conditions',
engines: '>= 14'
});
st.fail('should have thrown');
} catch (e) {
st.ok((/mutually exclusive/).test(e.message), 'throws with mutually exclusive message');
}
});
t.test('exportsCategory and engines (true) are mutually exclusive', function (st) {
st.plan(1);
try {
resolve('tape', {
basedir: __dirname,
exportsCategory: 'conditions',
engines: true
});
st.fail('should have thrown');
} catch (e) {
st.ok((/mutually exclusive/).test(e.message), 'throws with mutually exclusive message');
}
});
t.end();
});
test('exports resolution - invalid category', function (t) {
t.plan(2);
try {
resolve('tape', {
basedir: __dirname,
exportsCategory: 'not-a-real-category'
});
t.fail('should have thrown');
} catch (e) {
t.equal(e.code, 'INVALID_EXPORTS_CATEGORY', 'has correct error code');
t.ok((/Invalid exports category/).test(e.message), 'has correct error message');
}
});
test('exports resolution - engines option', function (t) {
var projectDir = path.join(fixturesDir, 'ex-exports-string', 'project');
t.test('engines string maps to category', function (st) {
st.plan(1);
var result = resolve('ex-exports-string', {
basedir: __dirname,
engines: '>= 14',
packageIterator: function () {
return [projectDir];
}
});
st.ok(result.indexOf('index.js') > -1, 'resolves to index.js');
});
t.test('engines: false is same as omitting', function (st) {
st.plan(1);
var result = resolve('tape', {
basedir: __dirname,
engines: false
});
st.ok(result.indexOf('tape') > -1, 'resolves without exports resolution');
});
t.test('engines: empty string throws', function (st) {
st.plan(1);
try {
resolve('tape', {
basedir: __dirname,
engines: ''
});
st.fail('should have thrown');
} catch (e) {
st.ok((/must be.*true.*false.*non-empty string/i).test(e.message), 'throws with correct message');
}
});
t.test('engines: number throws', function (st) {
st.plan(1);
try {
resolve('tape', {
basedir: __dirname,
engines: 14
});
st.fail('should have thrown');
} catch (e) {
st.ok((/must be.*true.*false.*non-empty string/i).test(e.message), 'throws with correct message');
}
});
t.test('engines: object throws', function (st) {
st.plan(1);
try {
resolve('tape', {
basedir: __dirname,
engines: { node: '>= 14' }
});
st.fail('should have thrown');
} catch (e) {
st.ok((/must be.*true.*false.*non-empty string/i).test(e.message), 'throws with correct message');
}
});
t.end();
});
test('exports resolution - conditions override', function (t) {
var projectDir = path.join(fixturesDir, 'ex-conditions', 'project');
t.test('default category conditions resolve to require.js', function (st) {
st.plan(1);
var result = resolve('ex-conditions/rdni', {
basedir: __dirname,
exportsCategory: 'conditions',
packageIterator: function () {
return [projectDir];
}
});
st.ok(result.indexOf('require.js') > -1, 'resolves to require.js with default conditions');
});
t.test('conditions override to [default] resolves to default.js', function (st) {
st.plan(1);
var result = resolve('ex-conditions/rdni', {
basedir: __dirname,
exportsCategory: 'conditions',
conditions: ['default'],
packageIterator: function () {
return [projectDir];
}
});
st.ok(result.indexOf('default.js') > -1, 'resolves to default.js with conditions override');
});
t.test('conditions override to [node] resolves to node.js', function (st) {
st.plan(1);
var result = resolve('ex-conditions/rdni', {
basedir: __dirname,
exportsCategory: 'conditions',
conditions: ['node'],
packageIterator: function () {
return [projectDir];
}
});
st.ok(result.indexOf('node.js') > -1, 'resolves to node.js with conditions override');
});
t.end();
});
test('exports resolution - subpath not exported throws', function (t) {
var projectDir = path.join(fixturesDir, 'ex-exports-string', 'project');
t.plan(2);
try {
resolve('ex-exports-string/not-exported', {
basedir: __dirname,
exportsCategory: 'conditions',
packageIterator: function () {
return [projectDir];
}
});
t.fail('should have thrown');
} catch (e) {
t.equal(e.code, 'ERR_PACKAGE_PATH_NOT_EXPORTED', 'has correct error code');
t.ok((/not defined by "exports"/).test(e.message), 'has correct error message');
}
});
test('existing resolution without exports options still works', function (t) {
t.plan(1);
var result = resolve('tape', { basedir: __dirname });
t.ok(result.indexOf('tape') > -1, 'resolves tape without exports options');
});
test('all fixtures are tested', function (t) {
var fixtures = getFixtures();
var testedFixtures = [];
fixtures.forEach(function (fixtureName) {
var projectPkg = loadProjectPkg(fixtureName);
if (!projectPkg) {
t.fail('Fixture ' + fixtureName + ' has no loadable package.json');
return;
}
var hasAnyTests = false;
// Check if at least one category has expected results
categories.forEach(function (category) {
var expected = loadExpected(fixtureName, category);
if (expected && expected.exports && expected.exports[category]) {
var requireMap = expected.exports[category].require;
if (requireMap && typeof requireMap === 'object' && Object.keys(requireMap).length > 0) {
hasAnyTests = true;
}
}
});
// Also check pre-exports
var preExpected = loadExpected(fixtureName, 'pre-exports');
if (preExpected && preExpected.exports && preExpected.exports['pre-exports']) {
var preRequireMap = preExpected.exports['pre-exports'].require;
if (preRequireMap && preRequireMap['.']) {
hasAnyTests = true;
}
}
if (hasAnyTests) {
testedFixtures.push(fixtureName);
} else {
t.fail('Fixture ' + fixtureName + ' has no testable entrypoints');
}
});
t.ok(testedFixtures.length > 0, 'At least one fixture is tested');
t.equal(testedFixtures.length, fixtures.length, 'All ' + fixtures.length + ' fixtures have testable entrypoints');
t.end();
});
test('exports resolution - self-reference', function (t) {
var projectDir = path.join(fixturesDir, 'ex-exports-string', 'project');
t.test('self-reference resolves via exports when inside package', function (st) {
st.plan(1);
// basedir is inside the package, specifier is the package name
var result = resolve('ex-exports-string', {
basedir: projectDir,
exportsCategory: 'conditions'
});
st.ok(result.indexOf('index.js') > -1, 'self-reference resolves to index.js via exports');
});
t.test('self-reference with subpath resolves via exports', function (st) {
var conditionsDir = path.join(fixturesDir, 'ex-conditions', 'project');
st.plan(1);
var result = resolve('ex-conditions/rdni', {
basedir: conditionsDir,
exportsCategory: 'conditions'
});
st.ok(result.indexOf('require.js') > -1, 'self-reference subpath resolves correctly');
});
t.test('self-reference without exports falls back to main', function (st) {
// Create a scenario where there's no exports field
var mainDotlessDir = path.join(fixturesDir, 'ex-main-dotless', 'project');
st.plan(1);
try {
var result = resolve('ex-main-dotless', {
basedir: mainDotlessDir,
exportsCategory: 'conditions'
});
st.ok(result.indexOf('main.js') > -1 || result.indexOf('index.js') > -1, 'self-reference without exports uses main/index');
} catch (e) {
// If it throws, that's also acceptable behavior (no exports means not exported)
st.ok(true, 'self-reference without exports throws or resolves');
}
});
t.test('self-reference does not cross node_modules boundary', function (st) {
// basedir is inside node_modules, should NOT self-reference parent package
var nodeModulesDir = path.join(__dirname, '..', 'node_modules', 'tape');
st.plan(1);
// Trying to resolve 'resolve' from inside node_modules/tape should NOT
// self-reference the root resolve package - it should use normal resolution
var result = resolve('resolve', {
basedir: nodeModulesDir,
exportsCategory: 'conditions'
});
// The result should be from node_modules, not from a self-reference
// (self-reference would give us the current working directory's resolve)
st.ok(result.indexOf('node_modules') > -1 || result === 'resolve', 'does not self-reference across node_modules');
});
t.end();
});