1import { ExpoConfig } from '@expo/config-types'; 2import fs from 'fs'; 3import { vol } from 'memfs'; 4import path from 'path'; 5 6import { getMainApplication, readAndroidManifestAsync } from '../Manifest'; 7import * as Updates from '../Updates'; 8 9const fixturesPath = path.resolve(__dirname, 'fixtures'); 10const sampleManifestPath = path.resolve(fixturesPath, 'react-native-AndroidManifest.xml'); 11const sampleCodeSigningCertificatePath = path.resolve(fixturesPath, 'codeSigningCertificate.pem'); 12 13jest.mock('fs'); 14jest.mock('resolve-from'); 15 16const { silent } = require('resolve-from'); 17 18const fsReal = jest.requireActual('fs') as typeof import('fs'); 19 20describe('Android Updates config', () => { 21 beforeEach(() => { 22 const resolveFrom = require('resolve-from'); 23 resolveFrom.silent = silent; 24 vol.reset(); 25 }); 26 27 it('set correct values in AndroidManifest.xml', async () => { 28 vol.fromJSON({ 29 '/blah/react-native-AndroidManifest.xml': fsReal.readFileSync(sampleManifestPath, 'utf-8'), 30 '/app/hello': fsReal.readFileSync(sampleCodeSigningCertificatePath, 'utf-8'), 31 }); 32 33 let androidManifestJson = await readAndroidManifestAsync( 34 '/blah/react-native-AndroidManifest.xml' 35 ); 36 const config: ExpoConfig = { 37 name: 'foo', 38 sdkVersion: '37.0.0', 39 slug: 'my-app', 40 owner: 'owner', 41 updates: { 42 enabled: false, 43 fallbackToCacheTimeout: 2000, 44 checkAutomatically: 'ON_ERROR_RECOVERY', 45 codeSigningCertificate: 'hello', 46 codeSigningMetadata: { 47 alg: 'rsa-v1_5-sha256', 48 keyid: 'test', 49 }, 50 }, 51 }; 52 androidManifestJson = Updates.setUpdatesConfig( 53 '/app', 54 config, 55 androidManifestJson, 56 'user', 57 '0.11.0' 58 ); 59 const mainApplication = getMainApplication(androidManifestJson); 60 61 const updateUrl = mainApplication['meta-data'].filter( 62 (e) => e.$['android:name'] === 'expo.modules.updates.EXPO_UPDATE_URL' 63 ); 64 expect(updateUrl).toHaveLength(1); 65 expect(updateUrl[0].$['android:value']).toMatch('https://exp.host/@owner/my-app'); 66 67 const sdkVersion = mainApplication['meta-data'].filter( 68 (e) => e.$['android:name'] === 'expo.modules.updates.EXPO_SDK_VERSION' 69 ); 70 expect(sdkVersion).toHaveLength(1); 71 expect(sdkVersion[0].$['android:value']).toMatch('37.0.0'); 72 73 const enabled = mainApplication['meta-data'].filter( 74 (e) => e.$['android:name'] === 'expo.modules.updates.ENABLED' 75 ); 76 expect(enabled).toHaveLength(1); 77 expect(enabled[0].$['android:value']).toMatch('false'); 78 79 const checkOnLaunch = mainApplication['meta-data'].filter( 80 (e) => e.$['android:name'] === 'expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH' 81 ); 82 expect(checkOnLaunch).toHaveLength(1); 83 expect(checkOnLaunch[0].$['android:value']).toMatch('ERROR_RECOVERY_ONLY'); 84 85 const timeout = mainApplication['meta-data'].filter( 86 (e) => e.$['android:name'] === 'expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS' 87 ); 88 expect(timeout).toHaveLength(1); 89 expect(timeout[0].$['android:value']).toMatch('2000'); 90 91 const codeSigningCertificate = mainApplication['meta-data'].filter( 92 (e) => e.$['android:name'] === 'expo.modules.updates.CODE_SIGNING_CERTIFICATE' 93 ); 94 expect(codeSigningCertificate).toHaveLength(1); 95 expect(codeSigningCertificate[0].$['android:value']).toMatch( 96 fsReal.readFileSync(sampleCodeSigningCertificatePath, 'utf-8') 97 ); 98 99 const codeSigningMetadata = mainApplication['meta-data'].filter( 100 (e) => e.$['android:name'] === 'expo.modules.updates.CODE_SIGNING_METADATA' 101 ); 102 expect(codeSigningMetadata).toHaveLength(1); 103 expect(codeSigningMetadata[0].$['android:value']).toMatch( 104 '{"alg":"rsa-v1_5-sha256","keyid":"test"}' 105 ); 106 }); 107 108 describe(Updates.ensureBuildGradleContainsConfigurationScript, () => { 109 it('adds create-manifest-android.gradle line to build.gradle', async () => { 110 vol.fromJSON( 111 { 112 'android/app/build.gradle': fsReal.readFileSync( 113 path.join(__dirname, 'fixtures/build-without-create-manifest-android.gradle'), 114 'utf-8' 115 ), 116 'node_modules/expo-updates/scripts/create-manifest-android.gradle': 'whatever', 117 }, 118 '/app' 119 ); 120 121 const contents = await fs.promises.readFile('/app/android/app/build.gradle', 'utf-8'); 122 const newContents = Updates.ensureBuildGradleContainsConfigurationScript('/app', contents); 123 expect(newContents).toMatchSnapshot(); 124 }); 125 126 it('fixes the path to create-manifest-android.gradle in case of a monorepo', async () => { 127 // Pseudo node module resolution since actually mocking it could prove challenging. 128 // In a yarn workspace, resolve-from would be able to locate a module in any node_module folder if properly linked. 129 const resolveFrom = require('resolve-from'); 130 resolveFrom.silent = (p, a) => { 131 return silent(path.join(p, '..'), a); 132 }; 133 134 vol.fromJSON( 135 { 136 'workspace/android/app/build.gradle': fsReal.readFileSync( 137 path.join( 138 __dirname, 139 'fixtures/build-with-incorrect-create-manifest-android-path.gradle' 140 ), 141 'utf-8' 142 ), 143 'node_modules/expo-updates/scripts/create-manifest-android.gradle': 'whatever', 144 }, 145 '/app' 146 ); 147 148 const contents = await fs.promises.readFile( 149 '/app/workspace/android/app/build.gradle', 150 'utf-8' 151 ); 152 const newContents = Updates.ensureBuildGradleContainsConfigurationScript( 153 '/app/workspace', 154 contents 155 ); 156 expect(newContents).toMatchSnapshot(); 157 }); 158 }); 159}); 160