mirror of
https://github.com/callstack/react-native-testing-library.git
synced 2026-09-18 23:09:04 +08:00
190 lines
5.6 KiB
JavaScript
190 lines
5.6 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import type { Transform } from 'codemod:ast-grep';
|
|
import type JSONLang from 'codemod:ast-grep/langs/json';
|
|
|
|
const RNTL_VERSION = '^14.0.0-0';
|
|
const TEST_RENDERER_FALLBACK_VERSION = '^1.2.0';
|
|
const TEST_RENDERER_VERSION_BY_REACT_MINOR: Record<string, string> = {
|
|
'19.0': '^1.0.0',
|
|
'19.1': '^1.1.0',
|
|
'19.2': '^1.2.0',
|
|
};
|
|
|
|
interface PackageJson {
|
|
dependencies?: Record<string, string>;
|
|
devDependencies?: Record<string, string>;
|
|
peerDependencies?: Record<string, string>;
|
|
optionalDependencies?: Record<string, string>;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export default async function transform(
|
|
root: Parameters<Transform<JSONLang>>[0],
|
|
): Promise<string | null> {
|
|
const filename = root.filename();
|
|
|
|
if (!isPackageJsonFile(filename)) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const content = root.root().text();
|
|
const packageJson: PackageJson = JSON.parse(content);
|
|
|
|
if (!hasRntlOrTestRenderer(packageJson)) {
|
|
return null;
|
|
}
|
|
|
|
let hasChanges = false;
|
|
|
|
if (removeObsoletePackages(packageJson)) {
|
|
hasChanges = true;
|
|
}
|
|
|
|
if (ensureDevDependenciesObjectExists(packageJson)) {
|
|
hasChanges = true;
|
|
}
|
|
|
|
if (ensureRntlInDevDependencies(packageJson)) {
|
|
hasChanges = true;
|
|
}
|
|
|
|
if (updateTestRendererVersionInDevDependencies(packageJson)) {
|
|
hasChanges = true;
|
|
}
|
|
|
|
if (hasChanges) {
|
|
return JSON.stringify(packageJson, null, 2) + '\n';
|
|
}
|
|
|
|
return null;
|
|
} catch (error) {
|
|
// Re-throw error to let the codemod platform handle it
|
|
// This provides better error reporting than silently returning null
|
|
throw new Error(
|
|
`Error processing ${filename}: ${error instanceof Error ? error.message : String(error)}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
function isPackageJsonFile(filename: string): boolean {
|
|
return filename.endsWith('package.json');
|
|
}
|
|
|
|
function hasRntlOrTestRenderer(packageJson: PackageJson): boolean {
|
|
const hasRntl =
|
|
packageJson.dependencies?.['@testing-library/react-native'] ||
|
|
packageJson.devDependencies?.['@testing-library/react-native'] ||
|
|
packageJson.peerDependencies?.['@testing-library/react-native'];
|
|
|
|
const hasTestRenderer =
|
|
packageJson.dependencies?.['test-renderer'] ||
|
|
packageJson.devDependencies?.['test-renderer'] ||
|
|
packageJson.peerDependencies?.['test-renderer'];
|
|
|
|
return hasRntl || hasTestRenderer;
|
|
}
|
|
|
|
function removePackageFromAllDependencyTypes(pkgName: string, packageJson: PackageJson): boolean {
|
|
let removed = false;
|
|
(
|
|
['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'] as const
|
|
).forEach((depType) => {
|
|
if (packageJson[depType]?.[pkgName]) {
|
|
delete packageJson[depType]![pkgName];
|
|
removed = true;
|
|
if (Object.keys(packageJson[depType]!).length === 0) {
|
|
delete packageJson[depType];
|
|
}
|
|
}
|
|
});
|
|
return removed;
|
|
}
|
|
|
|
function removeEmptyDependencyObject(packageJson: PackageJson, depType: keyof PackageJson): void {
|
|
const deps = packageJson[depType];
|
|
if (deps && typeof deps === 'object' && !Array.isArray(deps) && !Object.keys(deps).length) {
|
|
delete packageJson[depType];
|
|
}
|
|
}
|
|
|
|
function ensureDevDependenciesObjectExists(packageJson: PackageJson): boolean {
|
|
if (!packageJson.devDependencies) {
|
|
packageJson.devDependencies = {};
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function removeObsoletePackages(packageJson: PackageJson): boolean {
|
|
const removedTypes = removePackageFromAllDependencyTypes(
|
|
'@types/react-test-renderer',
|
|
packageJson,
|
|
);
|
|
const removedRenderer = removePackageFromAllDependencyTypes('react-test-renderer', packageJson);
|
|
return removedTypes || removedRenderer;
|
|
}
|
|
|
|
function ensureRntlInDevDependencies(packageJson: PackageJson): boolean {
|
|
let hasChanges = false;
|
|
const rntlInDeps = packageJson.dependencies?.['@testing-library/react-native'];
|
|
|
|
if (rntlInDeps) {
|
|
delete packageJson.dependencies!['@testing-library/react-native'];
|
|
removeEmptyDependencyObject(packageJson, 'dependencies');
|
|
hasChanges = true;
|
|
}
|
|
|
|
const currentVersion = packageJson.devDependencies?.['@testing-library/react-native'];
|
|
if (currentVersion !== RNTL_VERSION) {
|
|
packageJson.devDependencies!['@testing-library/react-native'] = RNTL_VERSION;
|
|
hasChanges = true;
|
|
}
|
|
|
|
return hasChanges;
|
|
}
|
|
|
|
function updateTestRendererVersionInDevDependencies(packageJson: PackageJson): boolean {
|
|
const expectedVersion = getTestRendererVersion(packageJson);
|
|
const currentVersion = packageJson.devDependencies?.['test-renderer'];
|
|
if (currentVersion !== expectedVersion) {
|
|
packageJson.devDependencies!['test-renderer'] = expectedVersion;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getTestRendererVersion(packageJson: PackageJson): string {
|
|
const reactVersion = getPackageVersion(packageJson, 'react');
|
|
const reactMinor = reactVersion ? getKnownReact19Minor(reactVersion) : null;
|
|
|
|
return reactMinor
|
|
? TEST_RENDERER_VERSION_BY_REACT_MINOR[reactMinor]
|
|
: TEST_RENDERER_FALLBACK_VERSION;
|
|
}
|
|
|
|
function getPackageVersion(packageJson: PackageJson, pkgName: string): string | undefined {
|
|
for (const depType of [
|
|
'dependencies',
|
|
'devDependencies',
|
|
'peerDependencies',
|
|
'optionalDependencies',
|
|
] as const) {
|
|
const version = packageJson[depType]?.[pkgName];
|
|
if (version) {
|
|
return version;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function getKnownReact19Minor(version: string): string | null {
|
|
const trimmedVersion = version.trim();
|
|
const exactVersionMatch = trimmedVersion.match(/^=?~?(19\.(?:0|1|2))(?:\.\d+)?(?:-[\w.-]+)?$/);
|
|
const xRangeMatch = trimmedVersion.match(/^~?(19\.(?:0|1|2))\.(?:x|\*)$/i);
|
|
|
|
return exactVersionMatch?.[1] ?? xRangeMatch?.[1] ?? null;
|
|
}
|