Skip to content

Commit 8ce14cf

Browse files
committed
add tests for resolving transitive deps
1 parent 68dc7e8 commit 8ce14cf

File tree

2 files changed

+290
-27
lines changed

2 files changed

+290
-27
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import path from 'path';
2+
import prompts from 'prompts';
3+
import {cleanup, getTempDirectory, writeFiles} from '../../../../jest/helpers';
4+
import {
5+
DependencyData,
6+
calculateWorkingVersion,
7+
collectDependencies,
8+
filterInstalledPeers,
9+
filterNativeDependencies,
10+
findDependencyPath,
11+
getMissingPeerDepsForYarn,
12+
resolveTransitiveDeps,
13+
} from '../resolveTransitiveDeps';
14+
import logger from '../logger';
15+
16+
jest.mock('execa', () => {
17+
return {sync: jest.fn()};
18+
});
19+
20+
jest.mock('prompts', () => ({prompt: jest.fn()}));
21+
22+
jest.mock('../logger', () => ({
23+
isVerbose: jest.fn(),
24+
warn: jest.fn(),
25+
log: jest.fn(),
26+
}));
27+
28+
const mockFetchJson = jest.fn();
29+
30+
jest.mock('npm-registry-fetch', () => ({
31+
json: mockFetchJson,
32+
}));
33+
34+
const rootPackageJson = {
35+
name: 'App',
36+
version: '1.0.0',
37+
dependencies: {
38+
'react-native': '0.72.4',
39+
'@react-navigation/stack': '^6.3.17',
40+
},
41+
};
42+
43+
const stackPackageJson = {
44+
name: '@react-navigation/stack',
45+
version: '6.3.17',
46+
dependencies: {
47+
'@react-navigation/elements': '^1.3.18',
48+
'react-native-gesture-handler': '^1.10.3',
49+
},
50+
peerDependencies: {
51+
react: '*',
52+
'react-native': '*',
53+
'react-native-gesture-handler': '>= 1.0.0',
54+
},
55+
};
56+
57+
const elementsPackageJson = {
58+
name: '@react-navigation/elements',
59+
version: '1.3.18',
60+
peerDependencies: {
61+
react: '*',
62+
'react-native': '*',
63+
'react-native-safe-area-view': '*',
64+
},
65+
};
66+
67+
const gestureHandlerPackageJson = {
68+
name: 'react-native-gesture-handler',
69+
version: '1.10.3',
70+
};
71+
72+
const DIR = getTempDirectory('root_test');
73+
74+
const createTempFiles = (rest?: Record<string, string>) => {
75+
writeFiles(DIR, {
76+
'package.json': JSON.stringify(rootPackageJson),
77+
'node_modules/@react-navigation/stack/package.json': JSON.stringify(
78+
stackPackageJson,
79+
),
80+
'node_modules/@react-navigation/elements/package.json': JSON.stringify(
81+
elementsPackageJson,
82+
),
83+
'node_modules/react-native-gesture-handler/package.json': JSON.stringify(
84+
gestureHandlerPackageJson,
85+
),
86+
'node_modules/react-native-gesture-handler/ios/Podfile': '',
87+
...rest,
88+
});
89+
};
90+
91+
beforeEach(async () => {
92+
await cleanup(DIR);
93+
jest.resetAllMocks();
94+
});
95+
96+
describe('calculateWorkingVersion', () => {
97+
it('should return the highest matching version for all ranges', () => {
98+
const workingVersion = calculateWorkingVersion(
99+
['*', '>=2.2.0', '>=2.0.0'],
100+
['1.9.0', '2.0.0', '2.2.0', '3.0.0'],
101+
);
102+
103+
expect(workingVersion).toBe('3.0.0');
104+
});
105+
106+
it('should return null if no version matches all ranges', () => {
107+
const workingVersion = calculateWorkingVersion(
108+
['*', '>=2.2.0', '^1.0.0-alpha'],
109+
['1.9.0', '2.0.0', '2.1.0'],
110+
);
111+
112+
expect(workingVersion).toBe(null);
113+
});
114+
});
115+
116+
describe('findDependencyPath', () => {
117+
it('should return the path to the dependency if it is in top-level node_modules', () => {
118+
writeFiles(DIR, {
119+
'package.json': JSON.stringify(rootPackageJson),
120+
'node_modules/@react-navigation/stack/package.json': JSON.stringify(
121+
stackPackageJson,
122+
),
123+
});
124+
125+
const dependencyPath = findDependencyPath(
126+
'@react-navigation/stack',
127+
DIR,
128+
path.join(DIR, 'node_modules', '@react-navigation/stack'),
129+
);
130+
131+
expect(dependencyPath).toBe(
132+
path.join(DIR, 'node_modules', '@react-navigation/stack'),
133+
);
134+
});
135+
136+
it('should return the path to the nested node_modules if package is installed here', () => {
137+
writeFiles(DIR, {
138+
'package.json': JSON.stringify(rootPackageJson),
139+
'node_modules/@react-navigation/stack/node_modules/react-native-gesture-handler/package.json':
140+
'{}',
141+
});
142+
143+
const dependencyPath = findDependencyPath(
144+
'react-native-gesture-handler',
145+
DIR,
146+
path.join(DIR, 'node_modules', '@react-navigation/stack'),
147+
);
148+
149+
expect(dependencyPath).toBe(
150+
path.join(
151+
DIR,
152+
'node_modules',
153+
'@react-navigation/stack',
154+
'node_modules',
155+
'react-native-gesture-handler',
156+
),
157+
);
158+
});
159+
});
160+
161+
describe('collectDependencies', () => {
162+
beforeEach(() => {
163+
createTempFiles();
164+
});
165+
166+
it('should recursively get all dependencies', () => {
167+
const dependencies = collectDependencies(DIR);
168+
169+
expect(dependencies.size).toBe(4);
170+
});
171+
172+
it('should collect peer dependencies of a dependency', () => {
173+
const dependencies = collectDependencies(DIR);
174+
const stackDependency = dependencies.get(
175+
'@react-navigation/stack',
176+
) as DependencyData;
177+
const peerNames = Object.keys(stackDependency.peerDependencies);
178+
179+
expect(peerNames).toContain('react');
180+
expect(peerNames).toContain('react-native');
181+
expect(peerNames).toContain('react-native-gesture-handler');
182+
});
183+
});
184+
185+
describe('filterNativeDependencies', () => {
186+
it('should return only dependencies with peer dependencies containing native code', () => {
187+
createTempFiles({
188+
'node_modules/react-native-safe-area-view/ios/Podfile': '{}',
189+
});
190+
const dependencies = collectDependencies(DIR);
191+
const filtered = filterNativeDependencies(DIR, dependencies);
192+
expect(filtered.keys()).toContain('@react-navigation/stack');
193+
expect(filtered.keys()).toContain('@react-navigation/elements');
194+
});
195+
});
196+
197+
describe('filterInstalledPeers', () => {
198+
it('should return only dependencies with peer dependencies that are installed', () => {
199+
createTempFiles();
200+
const dependencies = collectDependencies(DIR);
201+
const libsWithNativeDeps = filterNativeDependencies(DIR, dependencies);
202+
const nonInstalledPeers = filterInstalledPeers(DIR, libsWithNativeDeps);
203+
204+
expect(Object.keys(nonInstalledPeers)).toContain('@react-navigation/stack');
205+
expect(Object.keys(nonInstalledPeers['@react-navigation/stack'])).toContain(
206+
'react-native-gesture-handler',
207+
);
208+
});
209+
});
210+
211+
describe('getMissingPeerDepsForYarn', () => {
212+
it('should return an array of peer dependencies to install', () => {
213+
createTempFiles();
214+
215+
const dependencies = getMissingPeerDepsForYarn(DIR);
216+
expect(dependencies.values()).toContain('react');
217+
expect(dependencies.values()).toContain('react-native-gesture-handler');
218+
expect(dependencies.values()).toContain('react-native-safe-area-view');
219+
});
220+
});
221+
222+
describe('resolveTransitiveDeps', () => {
223+
it('should display list of missing peer dependencies if there are any', async () => {
224+
createTempFiles();
225+
prompts.prompt.mockReturnValue({});
226+
await resolveTransitiveDeps(DIR);
227+
expect(logger.warn).toHaveBeenCalledWith(
228+
'Looks like you are missing some of the peer dependencies of your libraries:\n',
229+
);
230+
});
231+
232+
it('should not display list if there are no missing peer dependencies', async () => {
233+
writeFiles(DIR, {
234+
'package.json': JSON.stringify(rootPackageJson),
235+
});
236+
237+
await resolveTransitiveDeps(DIR);
238+
expect(logger.warn).not.toHaveBeenCalled();
239+
});
240+
241+
it('should prompt user to install missing peer dependencies', async () => {
242+
createTempFiles();
243+
prompts.prompt.mockReturnValue({});
244+
await resolveTransitiveDeps(DIR);
245+
expect(prompts.prompt).toHaveBeenCalledWith({
246+
type: 'confirm',
247+
name: 'install',
248+
message:
249+
'Do you want to install them now? The matching versions will be added as project dependencies and become visible for autolinking.',
250+
});
251+
});
252+
253+
it('should install missing peer dependencies if user confirms', async () => {
254+
createTempFiles();
255+
prompts.prompt.mockReturnValue({install: true});
256+
mockFetchJson.mockReturnValueOnce({
257+
versions: {
258+
'2.0.0': {},
259+
'2.1.0': {},
260+
},
261+
});
262+
263+
const resolveDeps = await resolveTransitiveDeps(DIR);
264+
265+
expect(resolveDeps).toBe(true);
266+
});
267+
});

0 commit comments

Comments
 (0)