1import { ExpoConfig } from '@expo/config-types'; 2import glob from 'glob'; 3import { vol } from 'memfs'; 4import path from 'path'; 5 6import { withEntitlementsPlist, withInfoPlist } from '../ios-plugins'; 7import { evalModsAsync } from '../mod-compiler'; 8import { getIosModFileProviders, withIosBaseMods } from '../withIosBaseMods'; 9 10jest.mock('fs'); 11jest.mock('glob'); 12 13describe('entitlements', () => { 14 afterEach(() => { 15 vol.reset(); 16 }); 17 18 it(`evaluates in dry run mode`, async () => { 19 // Ensure this test runs in a blank file system 20 vol.fromJSON({}); 21 let config: ExpoConfig = { name: 'bacon', slug: 'bacon' }; 22 config = withEntitlementsPlist(config, (config) => { 23 config.modResults['haha'] = 'bet'; 24 return config; 25 }); 26 27 // base mods must be added last 28 config = withIosBaseMods(config, { 29 saveToInternal: true, 30 providers: { 31 entitlements: { 32 getFilePath() { 33 return ''; 34 }, 35 async read() { 36 return {}; 37 }, 38 async write() {}, 39 }, 40 }, 41 }); 42 config = await evalModsAsync(config, { projectRoot: '/', platforms: ['ios'] }); 43 44 expect(config.ios?.entitlements).toStrictEqual({ 45 haha: 'bet', 46 }); 47 // @ts-ignore: mods are untyped 48 expect(config.mods.ios.entitlements).toBeDefined(); 49 50 expect(config._internal.modResults.ios.entitlements).toBeDefined(); 51 52 // Ensure no files were written 53 expect(vol.toJSON()).toStrictEqual({}); 54 }); 55 56 it('uses local entitlement files by default', async () => { 57 // Create a fake project that can load entitlements 58 vol.fromJSON({ 59 '/ios/HelloWorld/AppDelegate.mm': 'Fake AppDelegate.mm', 60 '/ios/HelloWorld.xcodeproj/project.pbxproj': jest 61 .requireActual<typeof import('fs')>('fs') 62 .readFileSync( 63 path.resolve(__dirname, './fixtures/project-files/ios/project.pbxproj'), 64 'utf-8' 65 ), 66 '/ios/HelloWorld/HelloWorld.entitlements': jest 67 .requireActual<typeof import('fs')>('fs') 68 .readFileSync( 69 path.resolve(__dirname, './fixtures/project-files/ios/project.entitlements'), 70 'utf-8' 71 ), 72 }); 73 74 // Mock glob response to "find" the memfs files 75 jest.mocked(glob.sync).mockImplementation((pattern) => { 76 if (pattern === 'ios/**/*.xcodeproj') return ['/ios/HelloWorld.xcodeproj']; 77 if (pattern === 'ios/*/AppDelegate.@(m|mm|swift)') return ['/ios/HelloWorld/AppDelegate.mm']; 78 throw new Error('Unexpected glob pattern used in test'); 79 }); 80 81 // Create simple project config and config plugin chain 82 let config: ExpoConfig = { name: 'bacon', slug: 'bacon' }; 83 config = withEntitlementsPlist(config, (config) => { 84 config.modResults['haha'] = 'yes'; 85 return config; 86 }); 87 88 // Base mod must be added last 89 config = withIosBaseMods(config, { 90 saveToInternal: true, 91 providers: { 92 // Use the default mod provider, that's the one we need to test 93 entitlements: getIosModFileProviders().entitlements, 94 }, 95 }); 96 config = await evalModsAsync(config, { 97 projectRoot: '/', 98 platforms: ['ios'], 99 }); 100 101 // Check if the generated entitlements are merged with local entitlements 102 expect(config.ios?.entitlements).toMatchInlineSnapshot(` 103 { 104 "aps-environment": "development", 105 "com.apple.developer.applesignin": [ 106 "Default", 107 ], 108 "com.apple.developer.associated-domains": [ 109 "applinks:acme.com", 110 ], 111 "com.apple.developer.icloud-container-identifiers": [ 112 "iCloud.$(CFBundleIdentifier)", 113 ], 114 "com.apple.developer.icloud-services": [ 115 "CloudDocuments", 116 ], 117 "com.apple.developer.ubiquity-container-identifiers": [ 118 "iCloud.$(CFBundleIdentifier)", 119 ], 120 "com.apple.developer.ubiquity-kvstore-identifier": "$(TeamIdentifierPrefix)$(CFBundleIdentifier)", 121 "haha": "yes", 122 } 123 `); 124 }); 125 126 it('skips local entitlements files when ignoring existing native files', async () => { 127 // Create a fake project that can load entitlements 128 vol.fromJSON({ 129 '/ios/HelloWorld/AppDelegate.mm': 'Fake AppDelegate.mm', 130 '/ios/HelloWorld.xcodeproj/project.pbxproj': jest 131 .requireActual<typeof import('fs')>('fs') 132 .readFileSync( 133 path.resolve(__dirname, './fixtures/project-files/ios/project.pbxproj'), 134 'utf-8' 135 ), 136 '/ios/HelloWorld/HelloWorld.entitlements': jest 137 .requireActual<typeof import('fs')>('fs') 138 .readFileSync( 139 path.resolve(__dirname, './fixtures/project-files/ios/project.entitlements'), 140 'utf-8' 141 ), 142 }); 143 144 // Mock glob response to "find" the memfs files 145 jest.mocked(glob.sync).mockImplementation((pattern) => { 146 if (pattern === 'ios/**/*.xcodeproj') return ['/ios/HelloWorld.xcodeproj']; 147 if (pattern === 'ios/*/AppDelegate.@(m|mm|swift)') return ['/ios/HelloWorld/AppDelegate.mm']; 148 throw new Error('Unexpected glob pattern used in test'); 149 }); 150 151 // Create simple project config and config plugin chain 152 let config: ExpoConfig = { name: 'bacon', slug: 'bacon' }; 153 config = withEntitlementsPlist(config, (config) => { 154 config.modResults['haha'] = 'yes'; 155 return config; 156 }); 157 158 // Base mod must be added last 159 config = withIosBaseMods(config, { 160 saveToInternal: true, 161 providers: { 162 // Use the default mod provider, that's the one we need to test 163 entitlements: getIosModFileProviders().entitlements, 164 }, 165 }); 166 config = await evalModsAsync(config, { 167 projectRoot: '/', 168 platforms: ['ios'], 169 ignoreExistingNativeFiles: true, 170 }); 171 172 // Check if the generated entitlements are NOT merged with local entitlements 173 expect(config.ios?.entitlements).toMatchInlineSnapshot(` 174 { 175 "haha": "yes", 176 } 177 `); 178 }); 179}); 180 181describe('infoPlist', () => { 182 afterEach(() => { 183 vol.reset(); 184 }); 185 186 it(`evaluates in dry run mode`, async () => { 187 // Ensure this test runs in a blank file system 188 vol.fromJSON({}); 189 let config: ExpoConfig = { name: 'bacon', slug: 'bacon' }; 190 config = withInfoPlist(config, (config) => { 191 config.modResults['haha'] = 'bet'; 192 return config; 193 }); 194 195 // base mods must be added last 196 config = withIosBaseMods(config, { 197 saveToInternal: true, 198 providers: { 199 infoPlist: { 200 getFilePath() { 201 return ''; 202 }, 203 async read() { 204 return {}; 205 }, 206 async write() {}, 207 }, 208 }, 209 }); 210 config = await evalModsAsync(config, { projectRoot: '/', platforms: ['ios'] }); 211 212 expect(config.ios?.infoPlist).toStrictEqual({ 213 haha: 'bet', 214 }); 215 // @ts-ignore: mods are untyped 216 expect(config.mods.ios.infoPlist).toBeDefined(); 217 218 expect(config._internal.modResults.ios.infoPlist).toBeDefined(); 219 220 // Ensure no files were written 221 expect(vol.toJSON()).toStrictEqual({}); 222 }); 223}); 224