Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: metro-compat resolution tests #524

Merged
merged 47 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c55501c
fix: use metro defaults for getResolveOptions
jbroma Mar 15, 2024
f2438d2
fix: align conditonNames with @react-native/metro-config
jbroma Mar 15, 2024
0cb2684
fix: use platform extensions only when not using package exports
jbroma Mar 15, 2024
1c65c7e
feat: setup environment for metro tests
jbroma Mar 15, 2024
00e82cc
refactor: use imported utils
jbroma Mar 16, 2024
f126727
refactor: use ts for non-imported files
jbroma Mar 16, 2024
9aeed99
refactor: move metro tests to a separate workspace package
jbroma Mar 16, 2024
718cc37
chore: remove extra dev deps from repack, update lockfile
jbroma Mar 16, 2024
3110b05
chore: remove changes from eslintrc in repack
jbroma Mar 16, 2024
884a3a1
chore: remove changes in repack package setup
jbroma Mar 16, 2024
2de362a
Merge remote-tracking branch 'origin/v4' into fix-resolution-setup
jbroma Mar 16, 2024
aea383a
fix: eslint setup
jbroma Mar 16, 2024
2ef56fd
feat: add typing for ResolutionContext
jbroma Mar 16, 2024
e04a562
refactor: use utils mock
jbroma Mar 16, 2024
9739df8
chore: add enhanced-resolve for types
jbroma Mar 16, 2024
3f3a6fc
feat: setup resolve function in tests
jbroma Mar 16, 2024
f4ca8eb
chore: add @types/jest to metro-compat
jbroma Mar 16, 2024
c5a0749
feat: use imported utils module instead of own implementation
jbroma Mar 16, 2024
8722301
refactor: adjust resolve-error impl
jbroma Mar 16, 2024
5894205
feat: pass symlinks tests
jbroma Mar 16, 2024
c13267d
feat: add platform-extensions-test
jbroma Mar 16, 2024
24a97f0
feat: add package-exports tests
jbroma Mar 16, 2024
6380874
fix: don't apply js plugins to ts files
jbroma Mar 16, 2024
8d07477
refactor: use package json type from type-fest
jbroma Mar 16, 2024
3884132
refactor: obtain fileMap from createPackageAccessors instead
jbroma Mar 16, 2024
71c7f7e
refactor: use separate fields for fileMaps
jbroma Mar 16, 2024
7eaa54f
feat: add assets & browser-spec tests
jbroma Mar 16, 2024
e641a86
refactor: skip assets tests
jbroma Mar 16, 2024
bdd893d
feat: support more package exports cases
jbroma Mar 16, 2024
a679074
feat: add a list of tests to skip
jbroma Mar 17, 2024
51eefc5
feat: add index-tests
jbroma Mar 17, 2024
ec46559
refactor: cleanup resolve mock
jbroma Mar 17, 2024
7d7d812
refactor: use babel-jest directly, skip assets & index tests
jbroma Mar 17, 2024
900e71d
refactor: cleanup jest setup
jbroma Mar 17, 2024
d3917a2
refactor: add 2 more tests to skip list
jbroma Mar 17, 2024
0357d01
chore: exclude metro-compat from turbo test task
jbroma Mar 18, 2024
7174dc9
chore: revert changes to getResolveOptions in this PR
jbroma Mar 18, 2024
0a1c86e
chore: cleanup in test script
jbroma Mar 18, 2024
ae1ea21
chore: fix .prettierignore setup
jbroma Mar 18, 2024
afaa017
chore: cleanup .eslintrc.js in metro-compat
jbroma Mar 18, 2024
ff79061
chore: alter formatting and restore tests from upstream
jbroma Mar 18, 2024
6e4ec5b
chore: cleanup & add some utility scripts to metro-compat
jbroma Mar 18, 2024
c8e0095
fix: align to target getResolveOptions API
jbroma Mar 18, 2024
02fd645
chore: rename to metro-compat-test
jbroma Apr 2, 2024
4192706
chore: add README.md for metro-compat-test
jbroma Apr 2, 2024
57f6acf
chore: update pnpm lockfile
jbroma Apr 2, 2024
d7378d9
fix: update scripts in root package.json to reflect name change
jbroma Apr 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
metro-compat-test/**/__tests__/
5 changes: 3 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"css.validate": false
}
"css.validate": false,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
30 changes: 30 additions & 0 deletions metro-compat-test/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module.exports = {
extends: '@callstack/eslint-config/node',
parserOptions: {
project: true,
tsconfigRootDir: __dirname,
},
ignorePatterns: [
'.eslintrc.js',
'babel.config.js',
'jest.config.js',
'jest.setup.js',
'**/__tests__/*.js',
],
rules: {
'import/no-extraneous-dependencies': 'off',
},
settings: {
jest: {
version: 'latest',
},
},
overrides: [
{
files: ['jest.setup.js'],
env: {
jest: true,
},
},
],
};
4 changes: 4 additions & 0 deletions metro-compat-test/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
singleQuote: true,
trailingComma: 'es5',
};
25 changes: 25 additions & 0 deletions metro-compat-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# metro-compat-test

## Description

`metro-compat-test` is a package that uses the test suites from `metro-resolver` to ensure compatibility with `metro`, adapting them to work with `enhanced-resolve`.

## Adherence to metro spec

Some test suites are skipped altogether:

- `assets-test.js` - because `enhanced-resolve`, like node resolution, returns only a single entry, it's impossible to support these test cases. We handle this differently using `AssetResolver` which provides you with first matching entry for development, but all scales are exposed to the runtime.
- `index-test.js` - this suite contains some basic tests & failure tests which we could include but most of it contains tests for unsupported features like:
- `resolveRequest`
- `redirectModulePath`
- `disableHierarchicalLookup`

Some test cases are skipped (all come from `package-exports-test.js`):

- `[nonstrict]` tests are skipped because we don't support fallback in form of legacy file resolution
- asset tests with package exports enabled are skipped because they are handled differently.
- rest of the test skipped are due to inline snapshot not matching up with what `enhanced-resolve` returns

## Updating Tests

To update the tests, copy the latest test suites from the `metro-resolver/src/__tests__` directory in the [metro repository](https://github.com/facebook/metro/tree/main/packages/metro-resolver/src/__tests__) and paste them into `resolver/__tests__` dir.
51 changes: 51 additions & 0 deletions metro-compat-test/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// based on https://github.com/facebook/metro/blob/main/babel.config.js
const path = require('path');

const pathMap = {
resolver: {
'../index': './resolve.ts',
'../errors/FailedToResolvePathError': './resolve-error.ts',
// ignored
'../types': '../types',
},
};

const resolvePath = (sourcePath, currentFile) => {
const dir = path.basename(path.dirname(path.dirname(currentFile)));
if (sourcePath.startsWith('.') && pathMap[dir]?.[sourcePath]) {
return path.join(__dirname, dir, pathMap[dir][sourcePath]);
} else {
return require.resolve(sourcePath, { paths: [path.dirname(currentFile)] });
}
};

const jsPlugins = [
'babel-plugin-syntax-hermes-parser',
'@babel/plugin-transform-flow-strip-types',
'@babel/plugin-transform-modules-commonjs',
'@babel/plugin-syntax-class-properties',
];

const tsPlugins = [
'@babel/plugin-transform-typescript',
'@babel/plugin-transform-modules-commonjs',
'@babel/plugin-syntax-class-properties',
];

module.exports = {
babelrc: false,
browserslistConfigFile: false,
overrides: [
{
test: ['**/*.ts'],
plugins: tsPlugins.map((plugin) => require.resolve(plugin)),
},
{
test: ['**/*.js'],
plugins: [
...jsPlugins.map((plugin) => require.resolve(plugin)),
[require.resolve('babel-plugin-module-resolver'), { resolvePath }],
],
},
],
};
14 changes: 14 additions & 0 deletions metro-compat-test/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = {
rootDir: __dirname,
testEnvironment: 'node',
testRegex: '__tests__/.*-test\\.js$',
testPathIgnorePatterns: [
'<rootDir>/node_modules/',
// assets-test are not compatible with webpack's enhanced-resolve
'<rootDir>/resolver/__tests__/assets-test.js',
// index-test are almost entirely for legacy metro features which are not supported
'<rootDir>/resolver/__tests__/index-test.js',
],
transform: { '\\.[jt]s$': 'babel-jest' },
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
};
56 changes: 56 additions & 0 deletions metro-compat-test/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const testsToSkip = {
describe: new Set([
// unsupported in ehnanced-resolve as well
'[unsupported] exotic nested arrays',
]),
test: new Set([
// Non-strict package exports are not supported
'[nonstrict] should fall back to "main" field resolution when file does not exist',
'[nonstrict] should fall back to "main" field resolution when "exports" is an invalid subpath',
'[nonstrict] should fall back to "browser" spec resolution and log inaccessible import warning',
'[nonstrict] should fall back and log warning for an invalid "exports" target value',
'[nonstrict] should fall back to "browser" spec resolution and log inaccessible import warning',
'[nonstrict] should fall back to "browser" spec resolution and log inaccessible import warning',
// Assets are handled differently in webpack
'should resolve assets using "exports" field and calling `resolveAsset`',
// Resolving fails as expected but error messages are different
'should use most specific pattern base',
'should throw FailedToResolvePathError when no conditions are matched',
]),
};

// alias it to test
Object.defineProperty(testsToSkip, 'it', testsToSkip.test);

// trap call & check if test should be skipped
const handler = {
apply(target, _, args) {
if (testsToSkip[target.name].has(args[0])) {
return target.skip(...args);
} else return target(...args);
},
};

global.describe = new Proxy(global.describe, handler);
global.test = new Proxy(global.test, handler);
global.it = global.test;

// mock imported utils to gain access to __fileMap and __options
jest.doMock('./resolver/__tests__/utils', () => {
const originalModule = jest.requireActual('./resolver/__tests__/utils');
return {
createPackageAccessors(...args) {
return {
...originalModule.createPackageAccessors(...args),
__fileMapOverrides: args[0],
};
},
createResolutionContext(...args) {
return {
...originalModule.createResolutionContext(...args),
__fileMap: args[0],
__options: args[1],
};
},
};
});
29 changes: 29 additions & 0 deletions metro-compat-test/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "metro-compat-test",
"description": "Metro compatibility tests",
"author": "Jakub Romańczyk <jakub.romanczyk@callstack.com>",
"license": "MIT",
"private": true,
"engineStrict": true,
"engines": {
"node": ">=18"
},
"scripts": {
"lint": "eslint .",
"test": "jest --noStackTrace",
"typecheck": "tsc"
},
"devDependencies": {
"@callstack/repack": "workspace:*",
"@types/jest": "^28.1.1",
"@types/node": "^18",
"babel-jest": "^28.1.1",
"babel-plugin-module-resolver": "^5.0.0",
"babel-plugin-syntax-hermes-parser": "^0.20.1",
"enhanced-resolve": "^5.16.0",
"eslint": "^8.53.0",
"jest": "^28.1.1",
"memfs": "^3.4.4",
"type-fest": "^4.12.0"
}
}
86 changes: 86 additions & 0 deletions metro-compat-test/resolver/__tests__/assets-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/

'use strict';

import Resolver from '../index';
import {createResolutionContext} from './utils';
import path from 'path';

describe('asset resolutions', () => {
const baseContext = {
...createResolutionContext({
'/root/project/index.js': '',
'/root/project/src/data.json': '',
'/root/project/assets/example.asset.json': '',
'/root/project/assets/icon.png': '',
'/root/project/assets/icon@2x.png': '',
}),
originModulePath: '/root/project/index.js',
};
const assetResolutions = ['1', '2'];
const resolveAsset = (
dirPath: string,
assetName: string,
extension: string,
) => {
const basePath = dirPath + path.sep + assetName;
let assets = [
basePath + extension,
...assetResolutions.map(
resolution => basePath + '@' + resolution + 'x' + extension,
),
];

assets = assets.filter(candidate => baseContext.doesFileExist(candidate));

return assets.length ? assets : null;
};

test('should resolve a path as an asset when matched against `assetExts`', () => {
const context = {
...baseContext,
assetExts: new Set(['png']),
resolveAsset,
};

expect(Resolver.resolve(context, './assets/icon.png', null)).toEqual({
type: 'assetFiles',
filePaths: [
'/root/project/assets/icon.png',
'/root/project/assets/icon@2x.png',
],
});
});

test('should resolve a path as an asset when matched against `assetExts` (overlap with `sourceExts`)', () => {
const context = {
...baseContext,
assetExts: new Set(['asset.json']),
resolveAsset,
sourceExts: ['js', 'json'],
};

// Source file matching `sourceExts`
expect(Resolver.resolve(context, './src/data.json', null)).toEqual({
type: 'sourceFile',
filePath: '/root/project/src/data.json',
});

// Asset file matching more specific asset ext
expect(
Resolver.resolve(context, './assets/example.asset.json', null),
).toEqual({
type: 'assetFiles',
filePaths: ['/root/project/assets/example.asset.json'],
});
});
});
Loading