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