1import { ExpoConfig } from '@expo/config-types';
2import { fs as memfs, vol } from 'memfs';
3import path from 'path';
4
5import rnFixture from '../../plugins/__tests__/fixtures/react-native-project';
6import {
7  getBundleIdentifier,
8  getBundleIdentifierFromPbxproj,
9  setBundleIdentifier,
10  setBundleIdentifierForPbxproj,
11} from '../BundleIdentifier';
12
13const baseExpoConfig: ExpoConfig = {
14  name: 'testproject',
15  slug: 'testproject',
16  platforms: ['ios'],
17  version: '1.0.0',
18};
19
20jest.mock('fs');
21
22const originalFs = jest.requireActual('fs');
23
24describe('BundleIdentifier module', () => {
25  describe(getBundleIdentifier, () => {
26    it('returns null if no bundleIdentifier is provided', () => {
27      expect(getBundleIdentifier(baseExpoConfig)).toBe(null);
28    });
29
30    it('returns the bundleIdentifier if provided', () => {
31      expect(
32        getBundleIdentifier({ ...baseExpoConfig, ios: { bundleIdentifier: 'com.example.xyz' } })
33      ).toBe('com.example.xyz');
34    });
35  });
36
37  describe(getBundleIdentifierFromPbxproj, () => {
38    const projectRoot = '/testproject';
39
40    afterEach(() => vol.reset());
41
42    it('returns null if no project.pbxproj exists', () => {
43      vol.mkdirpSync(projectRoot);
44      const bundleId = getBundleIdentifierFromPbxproj(projectRoot, { targetName: 'testproject' });
45      expect(bundleId).toBeNull();
46    });
47
48    it('returns the bundle identifier defined in project.pbxproj', () => {
49      vol.fromJSON(
50        {
51          'ios/HelloWorld.xcodeproj/project.pbxproj':
52            rnFixture['ios/HelloWorld.xcodeproj/project.pbxproj'],
53        },
54        projectRoot
55      );
56      const bundleId = getBundleIdentifierFromPbxproj(projectRoot, { targetName: 'HelloWorld' });
57      expect(bundleId).toBe('org.name.HelloWorld');
58    });
59
60    describe('multi-target project', () => {
61      it('returns correct bundle identifier for the default build configuration (Release)', () => {
62        vol.fromJSON(
63          {
64            'ios/testproject.xcodeproj/project.pbxproj': originalFs.readFileSync(
65              path.join(__dirname, 'fixtures/project-multitarget.pbxproj'),
66              'utf-8'
67            ),
68            'ios/testproject.xcodeproj/xcshareddata/xcschemes/multitarget.xcscheme':
69              originalFs.readFileSync(
70                path.join(__dirname, 'fixtures/multitarget.xcscheme'),
71                'utf-8'
72              ),
73          },
74          projectRoot
75        );
76        const bundleId = getBundleIdentifierFromPbxproj(projectRoot, {
77          targetName: 'multitarget',
78        });
79        expect(bundleId).toBe('com.swmansion.dominik.multitarget');
80      });
81
82      it('returns correct bundle identifier for Debug build configuration', () => {
83        vol.fromJSON(
84          {
85            'ios/testproject.xcodeproj/project.pbxproj': originalFs.readFileSync(
86              path.join(__dirname, 'fixtures/project-multitarget.pbxproj'),
87              'utf-8'
88            ),
89            'ios/testproject.xcodeproj/xcshareddata/xcschemes/multitarget.xcscheme':
90              originalFs.readFileSync(
91                path.join(__dirname, 'fixtures/multitarget.xcscheme'),
92                'utf-8'
93              ),
94          },
95          projectRoot
96        );
97        const bundleId = getBundleIdentifierFromPbxproj(projectRoot, {
98          targetName: 'multitarget',
99          buildConfiguration: 'Debug',
100        });
101        expect(bundleId).toBe('com.swmansion.dominik.multitarget.debug');
102      });
103    });
104  });
105
106  describe(setBundleIdentifier, () => {
107    it('sets the CFBundleShortVersionString if bundleIdentifier is given', () => {
108      expect(
109        setBundleIdentifier(
110          { ...baseExpoConfig, ios: { bundleIdentifier: 'host.exp.exponent' } },
111          {}
112        )
113      ).toMatchObject({
114        CFBundleIdentifier: 'host.exp.exponent',
115      });
116    });
117
118    it('makes no changes to the infoPlist no bundleIdentifier is provided', () => {
119      expect(setBundleIdentifier(baseExpoConfig, {})).toMatchObject({});
120    });
121  });
122
123  describe(setBundleIdentifierForPbxproj, () => {
124    const projectRoot = '/testproject';
125    const pbxProjPath = 'ios/testproject.xcodeproj/project.pbxproj';
126    const otherPbxProjPath = 'ios/otherproject.xcodeproj/project.pbxproj';
127
128    beforeEach(() => {
129      vol.fromJSON(
130        {
131          [pbxProjPath]: rnFixture['ios/HelloWorld.xcodeproj/project.pbxproj'],
132          [otherPbxProjPath]: rnFixture['ios/HelloWorld.xcodeproj/project.pbxproj'],
133        },
134        projectRoot
135      );
136    });
137    afterEach(() => vol.reset());
138
139    it('sets the bundle identifier in the pbxproj file', () => {
140      setBundleIdentifierForPbxproj(projectRoot, 'com.swmansion.dominik.abcd.v2');
141      const pbxprojContents = memfs.readFileSync(path.join(projectRoot, pbxProjPath), 'utf-8');
142      const otherPbxprojContents = memfs.readFileSync(
143        path.join(projectRoot, otherPbxProjPath),
144        'utf-8'
145      );
146      expect(pbxprojContents).toMatchSnapshot();
147      // Ensure all paths are modified
148      expect(pbxprojContents).toBe(otherPbxprojContents);
149    });
150  });
151});
152