diff --git a/e2e/Utils.ts b/e2e/Utils.ts index a98e2e6bca39..f294fbb09138 100644 --- a/e2e/Utils.ts +++ b/e2e/Utils.ts @@ -90,6 +90,25 @@ export const writeFiles = ( }); }; +export const writeSymlinks = ( + directory: string, + symlinks: {[existingFile: string]: string}, +) => { + createDirectory(directory); + Object.keys(symlinks).forEach(fileOrPath => { + const symLinkPath = symlinks[fileOrPath]; + const dirname = path.dirname(symLinkPath); + + if (dirname !== '/') { + createDirectory(path.join(directory, dirname)); + } + fs.symlinkSync( + path.resolve(directory, ...fileOrPath.split('/')), + path.resolve(directory, ...symLinkPath.split('/')), + ); + }); +}; + const NUMBER_OF_TESTS_TO_FORCE_USING_WORKERS = 25; /** * Forces Jest to use workers by generating many test files to run. diff --git a/e2e/__tests__/resolveBrowserField.test.ts b/e2e/__tests__/resolveBrowserField.test.ts new file mode 100644 index 000000000000..761b25abbff3 --- /dev/null +++ b/e2e/__tests__/resolveBrowserField.test.ts @@ -0,0 +1,70 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {tmpdir} from 'os'; +import * as path from 'path'; +import {cleanup, writeFiles, writeSymlinks} from '../Utils'; +import runJest from '../runJest'; + +const DIR = path.resolve(tmpdir(), 'resolve-browser-field-test'); + +beforeEach(() => cleanup(DIR)); +afterAll(() => cleanup(DIR)); + +test('preserves module identity for symlinks when using browser field resolution', () => { + /* eslint-disable sort-keys */ + writeFiles(DIR, { + 'packages/needs-preserved-id/index.js': ` + console.log("needs-preserved-id executed"); + module.exports = {}; + `, + 'packages/needs-preserved-id/package.json': JSON.stringify({ + name: 'needs-preserved-id', + }), + 'packages/has-browser-field/package.json': JSON.stringify({ + name: 'has-browser-field', + browser: 'browser.js', + }), + 'packages/has-browser-field/browser.js': ` + module.exports = require("needs-preserved-id"); + `, + 'package.json': JSON.stringify({ + jest: { + testMatch: ['/test-files/test.js'], + browser: true, + }, + }), + 'test-files/test.js': ` + const id1 = require("needs-preserved-id"); + const id2 = require("has-browser-field"); + + test("module should have reference equality", () => { + expect(id1).toBe(id2); + }); + `, + }); + /* eslint-enable */ + + writeSymlinks(DIR, { + 'packages/has-browser-field': 'node_modules/has-browser-field', + 'packages/needs-preserved-id': 'node_modules/needs-preserved-id', + }); + + writeSymlinks(DIR, { + 'packages/needs-preserved-id': + 'packages/has-browser-field/node_modules/needs-preserved-id', + }); + + const {stdout, stderr, exitCode} = runJest(DIR, ['--no-watchman']); + expect(stderr).toContain('Test Suites: 1 passed, 1 total'); + expect(stdout).toMatchInlineSnapshot(` + " console.log packages/needs-preserved-id/index.js:2 + needs-preserved-id executed + " + `); + expect(exitCode).toEqual(0); +}); diff --git a/packages/jest-resolve/src/defaultResolver.ts b/packages/jest-resolve/src/defaultResolver.ts index 3c88694e5982..7dde191c71b5 100644 --- a/packages/jest-resolve/src/defaultResolver.ts +++ b/packages/jest-resolve/src/defaultResolver.ts @@ -8,6 +8,7 @@ import * as fs from 'fs'; import * as path from 'path'; import {sync as browserResolve} from 'browser-resolve'; +import {sync as realpath} from 'realpath-native'; import pnpResolver from 'jest-pnp-resolver'; import {Config} from '@jest/types'; import isBuiltinModule from './isBuiltinModule'; @@ -35,7 +36,7 @@ export default function defaultResolver( const resolve = options.browser ? browserResolve : resolveSync; - return resolve(path, { + let result = resolve(path, { basedir: options.basedir, defaultResolver, extensions: options.extensions, @@ -43,6 +44,12 @@ export default function defaultResolver( paths: options.paths, rootDir: options.rootDir, }); + if (options.browser && result) { + // Dereference symlinks to ensure we don't create a separate + // module instance depending on how it was referenced. + result = realpath(result); + } + return result; } export const clearDefaultResolverCache = () => { @@ -101,7 +108,7 @@ function resolveSync( if (result) { // Dereference symlinks to ensure we don't create a separate // module instance depending on how it was referenced. - result = fs.realpathSync(result); + result = realpath(result); } return result; }