-
Notifications
You must be signed in to change notification settings - Fork 905
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add tests for resolving transitive deps
- Loading branch information
1 parent
68dc7e8
commit 8ce14cf
Showing
2 changed files
with
290 additions
and
27 deletions.
There are no files selected for viewing
267 changes: 267 additions & 0 deletions
267
packages/cli-tools/src/__tests__/resolveTransitiveDeps.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
import path from 'path'; | ||
import prompts from 'prompts'; | ||
import {cleanup, getTempDirectory, writeFiles} from '../../../../jest/helpers'; | ||
import { | ||
DependencyData, | ||
calculateWorkingVersion, | ||
collectDependencies, | ||
filterInstalledPeers, | ||
filterNativeDependencies, | ||
findDependencyPath, | ||
getMissingPeerDepsForYarn, | ||
resolveTransitiveDeps, | ||
} from '../resolveTransitiveDeps'; | ||
import logger from '../logger'; | ||
|
||
jest.mock('execa', () => { | ||
return {sync: jest.fn()}; | ||
}); | ||
|
||
jest.mock('prompts', () => ({prompt: jest.fn()})); | ||
|
||
jest.mock('../logger', () => ({ | ||
isVerbose: jest.fn(), | ||
warn: jest.fn(), | ||
log: jest.fn(), | ||
})); | ||
|
||
const mockFetchJson = jest.fn(); | ||
|
||
jest.mock('npm-registry-fetch', () => ({ | ||
json: mockFetchJson, | ||
})); | ||
|
||
const rootPackageJson = { | ||
name: 'App', | ||
version: '1.0.0', | ||
dependencies: { | ||
'react-native': '0.72.4', | ||
'@react-navigation/stack': '^6.3.17', | ||
}, | ||
}; | ||
|
||
const stackPackageJson = { | ||
name: '@react-navigation/stack', | ||
version: '6.3.17', | ||
dependencies: { | ||
'@react-navigation/elements': '^1.3.18', | ||
'react-native-gesture-handler': '^1.10.3', | ||
}, | ||
peerDependencies: { | ||
react: '*', | ||
'react-native': '*', | ||
'react-native-gesture-handler': '>= 1.0.0', | ||
}, | ||
}; | ||
|
||
const elementsPackageJson = { | ||
name: '@react-navigation/elements', | ||
version: '1.3.18', | ||
peerDependencies: { | ||
react: '*', | ||
'react-native': '*', | ||
'react-native-safe-area-view': '*', | ||
}, | ||
}; | ||
|
||
const gestureHandlerPackageJson = { | ||
name: 'react-native-gesture-handler', | ||
version: '1.10.3', | ||
}; | ||
|
||
const DIR = getTempDirectory('root_test'); | ||
|
||
const createTempFiles = (rest?: Record<string, string>) => { | ||
writeFiles(DIR, { | ||
'package.json': JSON.stringify(rootPackageJson), | ||
'node_modules/@react-navigation/stack/package.json': JSON.stringify( | ||
stackPackageJson, | ||
), | ||
'node_modules/@react-navigation/elements/package.json': JSON.stringify( | ||
elementsPackageJson, | ||
), | ||
'node_modules/react-native-gesture-handler/package.json': JSON.stringify( | ||
gestureHandlerPackageJson, | ||
), | ||
'node_modules/react-native-gesture-handler/ios/Podfile': '', | ||
...rest, | ||
}); | ||
}; | ||
|
||
beforeEach(async () => { | ||
await cleanup(DIR); | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
describe('calculateWorkingVersion', () => { | ||
it('should return the highest matching version for all ranges', () => { | ||
const workingVersion = calculateWorkingVersion( | ||
['*', '>=2.2.0', '>=2.0.0'], | ||
['1.9.0', '2.0.0', '2.2.0', '3.0.0'], | ||
); | ||
|
||
expect(workingVersion).toBe('3.0.0'); | ||
}); | ||
|
||
it('should return null if no version matches all ranges', () => { | ||
const workingVersion = calculateWorkingVersion( | ||
['*', '>=2.2.0', '^1.0.0-alpha'], | ||
['1.9.0', '2.0.0', '2.1.0'], | ||
); | ||
|
||
expect(workingVersion).toBe(null); | ||
}); | ||
}); | ||
|
||
describe('findDependencyPath', () => { | ||
it('should return the path to the dependency if it is in top-level node_modules', () => { | ||
writeFiles(DIR, { | ||
'package.json': JSON.stringify(rootPackageJson), | ||
'node_modules/@react-navigation/stack/package.json': JSON.stringify( | ||
stackPackageJson, | ||
), | ||
}); | ||
|
||
const dependencyPath = findDependencyPath( | ||
'@react-navigation/stack', | ||
DIR, | ||
path.join(DIR, 'node_modules', '@react-navigation/stack'), | ||
); | ||
|
||
expect(dependencyPath).toBe( | ||
path.join(DIR, 'node_modules', '@react-navigation/stack'), | ||
); | ||
}); | ||
|
||
it('should return the path to the nested node_modules if package is installed here', () => { | ||
writeFiles(DIR, { | ||
'package.json': JSON.stringify(rootPackageJson), | ||
'node_modules/@react-navigation/stack/node_modules/react-native-gesture-handler/package.json': | ||
'{}', | ||
}); | ||
|
||
const dependencyPath = findDependencyPath( | ||
'react-native-gesture-handler', | ||
DIR, | ||
path.join(DIR, 'node_modules', '@react-navigation/stack'), | ||
); | ||
|
||
expect(dependencyPath).toBe( | ||
path.join( | ||
DIR, | ||
'node_modules', | ||
'@react-navigation/stack', | ||
'node_modules', | ||
'react-native-gesture-handler', | ||
), | ||
); | ||
}); | ||
}); | ||
|
||
describe('collectDependencies', () => { | ||
beforeEach(() => { | ||
createTempFiles(); | ||
}); | ||
|
||
it('should recursively get all dependencies', () => { | ||
const dependencies = collectDependencies(DIR); | ||
|
||
expect(dependencies.size).toBe(4); | ||
}); | ||
|
||
it('should collect peer dependencies of a dependency', () => { | ||
const dependencies = collectDependencies(DIR); | ||
const stackDependency = dependencies.get( | ||
'@react-navigation/stack', | ||
) as DependencyData; | ||
const peerNames = Object.keys(stackDependency.peerDependencies); | ||
|
||
expect(peerNames).toContain('react'); | ||
expect(peerNames).toContain('react-native'); | ||
expect(peerNames).toContain('react-native-gesture-handler'); | ||
}); | ||
}); | ||
|
||
describe('filterNativeDependencies', () => { | ||
it('should return only dependencies with peer dependencies containing native code', () => { | ||
createTempFiles({ | ||
'node_modules/react-native-safe-area-view/ios/Podfile': '{}', | ||
}); | ||
const dependencies = collectDependencies(DIR); | ||
const filtered = filterNativeDependencies(DIR, dependencies); | ||
expect(filtered.keys()).toContain('@react-navigation/stack'); | ||
expect(filtered.keys()).toContain('@react-navigation/elements'); | ||
}); | ||
}); | ||
|
||
describe('filterInstalledPeers', () => { | ||
it('should return only dependencies with peer dependencies that are installed', () => { | ||
createTempFiles(); | ||
const dependencies = collectDependencies(DIR); | ||
const libsWithNativeDeps = filterNativeDependencies(DIR, dependencies); | ||
const nonInstalledPeers = filterInstalledPeers(DIR, libsWithNativeDeps); | ||
|
||
expect(Object.keys(nonInstalledPeers)).toContain('@react-navigation/stack'); | ||
expect(Object.keys(nonInstalledPeers['@react-navigation/stack'])).toContain( | ||
'react-native-gesture-handler', | ||
); | ||
}); | ||
}); | ||
|
||
describe('getMissingPeerDepsForYarn', () => { | ||
it('should return an array of peer dependencies to install', () => { | ||
createTempFiles(); | ||
|
||
const dependencies = getMissingPeerDepsForYarn(DIR); | ||
expect(dependencies.values()).toContain('react'); | ||
expect(dependencies.values()).toContain('react-native-gesture-handler'); | ||
expect(dependencies.values()).toContain('react-native-safe-area-view'); | ||
}); | ||
}); | ||
|
||
describe('resolveTransitiveDeps', () => { | ||
it('should display list of missing peer dependencies if there are any', async () => { | ||
createTempFiles(); | ||
prompts.prompt.mockReturnValue({}); | ||
await resolveTransitiveDeps(DIR); | ||
expect(logger.warn).toHaveBeenCalledWith( | ||
'Looks like you are missing some of the peer dependencies of your libraries:\n', | ||
); | ||
}); | ||
|
||
it('should not display list if there are no missing peer dependencies', async () => { | ||
writeFiles(DIR, { | ||
'package.json': JSON.stringify(rootPackageJson), | ||
}); | ||
|
||
await resolveTransitiveDeps(DIR); | ||
expect(logger.warn).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should prompt user to install missing peer dependencies', async () => { | ||
createTempFiles(); | ||
prompts.prompt.mockReturnValue({}); | ||
await resolveTransitiveDeps(DIR); | ||
expect(prompts.prompt).toHaveBeenCalledWith({ | ||
type: 'confirm', | ||
name: 'install', | ||
message: | ||
'Do you want to install them now? The matching versions will be added as project dependencies and become visible for autolinking.', | ||
}); | ||
}); | ||
|
||
it('should install missing peer dependencies if user confirms', async () => { | ||
createTempFiles(); | ||
prompts.prompt.mockReturnValue({install: true}); | ||
mockFetchJson.mockReturnValueOnce({ | ||
versions: { | ||
'2.0.0': {}, | ||
'2.1.0': {}, | ||
}, | ||
}); | ||
|
||
const resolveDeps = await resolveTransitiveDeps(DIR); | ||
|
||
expect(resolveDeps).toBe(true); | ||
}); | ||
}); |
Oops, something went wrong.