1import * as fs from 'fs';
2import { vol } from 'memfs';
3import * as path from 'path';
4
5import { UnexpectedError } from '../../utils/errors';
6import * as WarningAggregator from '../../utils/warnings';
7import {
8  findSchemeNames,
9  getAllInfoPlistPaths,
10  getAppDelegate,
11  getXcodeProjectPath,
12} from '../Paths';
13
14const fsReal = jest.requireActual('fs') as typeof fs;
15
16jest.mock('fs');
17jest.mock('../../utils/warnings');
18
19describe(findSchemeNames, () => {
20  afterEach(() => {
21    vol.reset();
22  });
23
24  it(`returns project path`, () => {
25    vol.fromJSON(
26      {
27        'ios/my-app.xcodeproj/xcshareddata/xcschemes/my-app.xcscheme': '',
28        'ios/my_app.xcodeproj/xcshareddata/xcschemes/my_app.xcscheme': '',
29        'ios/clientTests.xcodeproj/xcshareddata/xcschemes/client.beta.xcscheme': '',
30      },
31      '/'
32    );
33
34    expect(findSchemeNames('/')).toStrictEqual(['client.beta', 'my_app', 'my-app']);
35  });
36});
37
38describe(getXcodeProjectPath, () => {
39  afterEach(() => {
40    vol.reset();
41  });
42
43  it(`returns project path`, () => {
44    vol.fromJSON(
45      {
46        'ios/testproject.xcodeproj/project.pbxproj': fsReal.readFileSync(
47          path.join(__dirname, 'fixtures/project.pbxproj'),
48          'utf-8'
49        ),
50        'ios/Podfile': 'content',
51        'ios/TestPod.podspec': 'noop',
52        'ios/testproject/AppDelegate.m': '',
53      },
54      '/app'
55    );
56    expect(getXcodeProjectPath('/app')).toBe('/app/ios/testproject.xcodeproj');
57  });
58
59  it(`throws when no paths are found`, () => {
60    expect(() => getXcodeProjectPath('/none')).toThrow(UnexpectedError);
61  });
62
63  it(`warns when multiple paths are found`, () => {
64    vol.fromJSON(
65      {
66        'ios/otherproject.xcodeproj/project.pbxproj': fsReal.readFileSync(
67          path.join(__dirname, 'fixtures/project.pbxproj'),
68          'utf-8'
69        ),
70        'ios/testproject.xcodeproj/project.pbxproj': fsReal.readFileSync(
71          path.join(__dirname, 'fixtures/project.pbxproj'),
72          'utf-8'
73        ),
74        'ios/testproject/AppDelegate.m': '',
75      },
76      '/app'
77    );
78
79    expect(getXcodeProjectPath('/app')).toBe('/app/ios/otherproject.xcodeproj');
80    expect(WarningAggregator.addWarningIOS).toHaveBeenLastCalledWith(
81      'paths-xcodeproj',
82      'Found multiple *.xcodeproj file paths, using "ios/otherproject.xcodeproj". Ignored paths: ["ios/testproject.xcodeproj"]'
83    );
84  });
85
86  it(`selects xcodeproj based on alphabetical order, but picks direct children of ios directory first`, () => {
87    vol.fromJSON(
88      {
89        'ios/otherproject.xcodeproj/project.pbxproj': fsReal.readFileSync(
90          path.join(__dirname, 'fixtures/project.pbxproj'),
91          'utf-8'
92        ),
93        'ios/aa/otherproject.xcodeproj/project.pbxproj': fsReal.readFileSync(
94          path.join(__dirname, 'fixtures/project.pbxproj'),
95          'utf-8'
96        ),
97        'ios/botherproject.xcodeproj/project.pbxproj': fsReal.readFileSync(
98          path.join(__dirname, 'fixtures/project.pbxproj'),
99          'utf-8'
100        ),
101        'ios/testproject.xcodeproj/project.pbxproj': fsReal.readFileSync(
102          path.join(__dirname, 'fixtures/project.pbxproj'),
103          'utf-8'
104        ),
105        'ios/testproject/AppDelegate.m': '',
106      },
107      '/app'
108    );
109
110    expect(getXcodeProjectPath('/app')).toBe('/app/ios/botherproject.xcodeproj');
111    expect(WarningAggregator.addWarningIOS).toHaveBeenLastCalledWith(
112      'paths-xcodeproj',
113      'Found multiple *.xcodeproj file paths, using "ios/botherproject.xcodeproj". Ignored paths: ["ios/otherproject.xcodeproj","ios/testproject.xcodeproj","ios/aa/otherproject.xcodeproj"]'
114    );
115  });
116
117  it(`warns when multiple paths are found`, () => {
118    vol.fromJSON(
119      {
120        'ios/otherproject.xcodeproj/project.pbxproj': fsReal.readFileSync(
121          path.join(__dirname, 'fixtures/project.pbxproj'),
122          'utf-8'
123        ),
124        'ios/testproject.xcodeproj/project.pbxproj': fsReal.readFileSync(
125          path.join(__dirname, 'fixtures/project.pbxproj'),
126          'utf-8'
127        ),
128        'ios/testproject/AppDelegate.m': '',
129      },
130      '/app'
131    );
132
133    expect(getXcodeProjectPath('/app')).toBe('/app/ios/otherproject.xcodeproj');
134    expect(WarningAggregator.addWarningIOS).toHaveBeenLastCalledWith(
135      'paths-xcodeproj',
136      'Found multiple *.xcodeproj file paths, using "ios/otherproject.xcodeproj". Ignored paths: ["ios/testproject.xcodeproj"]'
137    );
138  });
139  it(`ignores xcodeproj outside of the ios directory`, () => {
140    vol.fromJSON(
141      {
142        'lib/testproject.xcodeproj/project.pbxproj': fsReal.readFileSync(
143          path.join(__dirname, 'fixtures/project.pbxproj'),
144          'utf-8'
145        ),
146      },
147      '/app'
148    );
149    expect(() => getXcodeProjectPath('/app')).toThrow(UnexpectedError);
150  });
151});
152
153describe(getAppDelegate, () => {
154  beforeEach(() => {
155    vol.reset();
156  });
157  afterAll(() => {
158    vol.reset();
159  });
160
161  it(`returns objc path`, () => {
162    vol.fromJSON(
163      {
164        'ios/testproject.xcodeproj/project.pbxproj': fsReal.readFileSync(
165          path.join(__dirname, 'fixtures/project.pbxproj'),
166          'utf-8'
167        ) as string,
168        'ios/Podfile': 'content',
169        'ios/TestPod.podspec': 'noop',
170        'ios/testproject/AppDelegate.m': '',
171        'ios/testproject/AppDelegate.h': '',
172      },
173      '/objc'
174    );
175
176    expect(getAppDelegate('/objc')).toStrictEqual({
177      contents: '',
178      path: '/objc/ios/testproject/AppDelegate.m',
179      language: 'objc',
180    });
181  });
182
183  it(`returns C++ (objcpp) path`, () => {
184    vol.fromJSON(
185      {
186        'ios/testproject.xcodeproj/project.pbxproj': fsReal.readFileSync(
187          path.join(__dirname, 'fixtures/project.pbxproj'),
188          'utf-8'
189        ) as string,
190        'ios/Podfile': 'content',
191        'ios/TestPod.podspec': 'noop',
192        'ios/testproject/AppDelegate.mm': '',
193        'ios/testproject/AppDelegate.h': '',
194      },
195      '/'
196    );
197
198    expect(getAppDelegate('/')).toStrictEqual({
199      contents: '',
200      path: '/ios/testproject/AppDelegate.mm',
201      language: 'objcpp',
202    });
203  });
204
205  it(`returns swift path`, () => {
206    vol.fromJSON(
207      {
208        'ios/testproject.xcodeproj/project.pbxproj': fsReal.readFileSync(
209          path.join(__dirname, 'fixtures/project.pbxproj'),
210          'utf-8'
211        ) as string,
212        'ios/Podfile': 'content',
213        'ios/TestPod.podspec': 'noop',
214        'ios/testproject/AppDelegate.swift': '',
215      },
216      '/swift'
217    );
218
219    expect(getAppDelegate('/swift')).toStrictEqual({
220      contents: '',
221      path: '/swift/ios/testproject/AppDelegate.swift',
222      language: 'swift',
223    });
224  });
225
226  it(`throws on invalid project`, () => {
227    vol.fromJSON(
228      {
229        'ios/testproject.xcodeproj/project.pbxproj': fsReal.readFileSync(
230          path.join(__dirname, 'fixtures/project.pbxproj'),
231          'utf-8'
232        ) as string,
233        'ios/Podfile': 'content',
234        'ios/TestPod.podspec': 'noop',
235      },
236      '/invalid'
237    );
238
239    expect(() => getAppDelegate('/invalid')).toThrow(UnexpectedError);
240    expect(() => getAppDelegate('/invalid')).toThrow(/AppDelegate/);
241  });
242
243  it(`warns when multiple paths are found`, () => {
244    vol.fromJSON(
245      {
246        'ios/testproject.xcodeproj/project.pbxproj': fsReal.readFileSync(
247          path.join(__dirname, 'fixtures/project.pbxproj'),
248          'utf-8'
249        ) as string,
250        'ios/Podfile': 'content',
251        'ios/TestPod.podspec': 'noop',
252        'ios/testproject/AppDelegate.swift': '',
253        'ios/testproject/AppDelegate.m': '',
254        'ios/testproject/AppDelegate.h': '',
255      },
256      '/confusing'
257    );
258
259    expect(getAppDelegate('/confusing')).toStrictEqual({
260      contents: '',
261      path: '/confusing/ios/testproject/AppDelegate.m',
262      language: 'objc',
263    });
264    expect(WarningAggregator.addWarningIOS).toHaveBeenLastCalledWith(
265      'paths-app-delegate',
266      'Found multiple AppDelegate file paths, using "ios/testproject/AppDelegate.m". Ignored paths: ["ios/testproject/AppDelegate.swift"]'
267    );
268  });
269});
270
271describe(getAllInfoPlistPaths, () => {
272  beforeAll(async () => {
273    const project = {
274      'ExampleE2E-tvOS/Info.plist': '',
275      'ExampleE2E/Info.plist': '',
276      'ExampleE2E-tvOSTests/Info.plist': '',
277      'ExampleE2ETests/Info.plist': '',
278    };
279    vol.fromJSON(project, path.join('/app', 'ios'));
280    vol.fromJSON(project, '/app');
281  });
282
283  afterAll(() => {
284    vol.reset();
285  });
286
287  it(`gets paths in order`, () => {
288    expect(getAllInfoPlistPaths('/app')).toStrictEqual([
289      '/app/ios/ExampleE2E/Info.plist',
290      '/app/ios/ExampleE2E-tvOS/Info.plist',
291      '/app/ios/ExampleE2ETests/Info.plist',
292      '/app/ios/ExampleE2E-tvOSTests/Info.plist',
293    ]);
294  });
295});
296