1import { AndroidConfig } from '@expo/config-plugins'; 2import { ExpoConfig } from '@expo/config-types'; 3import * as fs from 'fs'; 4import { vol } from 'memfs'; 5import * as path from 'path'; 6 7import { 8 ADAPTIVE_ICON_XML_WITH_BACKGROUND_COLOR, 9 ADAPTIVE_ICON_XML_WITH_BOTH, 10 LIST_OF_ANDROID_ADAPTIVE_ICON_FILES_FINAL, 11 SAMPLE_COLORS_XML, 12} from '../../__tests__/fixtures/androidIcons'; 13import { 14 createAdaptiveIconXmlString, 15 getAdaptiveIcon, 16 getIcon, 17 setIconAsync, 18 setRoundIconManifest, 19} from '../withAndroidIcons'; 20import { getDirFromFS } from './utils/getDirFromFS'; 21const { getMainApplicationOrThrow, readAndroidManifestAsync } = AndroidConfig.Manifest; 22 23const sampleManifestPath = path.resolve( 24 __dirname, 25 '../../__tests__/fixtures', 26 'react-native-AndroidManifest.xml' 27); 28const fsReal = jest.requireActual('fs') as typeof fs; 29 30jest.mock('fs'); 31 32function setUpMipmapDirectories() { 33 vol.mkdirpSync('/app/android/app/src/main/res/mipmap-mdpi'); 34 vol.mkdirpSync('/app/android/app/src/main/res/mipmap-hdpi'); 35 vol.mkdirpSync('/app/android/app/src/main/res/mipmap-xhdpi'); 36 vol.mkdirpSync('/app/android/app/src/main/res/mipmap-xxhdpi'); 37 vol.mkdirpSync('/app/android/app/src/main/res/mipmap-xxxhdpi'); 38} 39 40describe(setRoundIconManifest, () => { 41 vol.fromJSON({ './AndroidManifest.xml': fsReal.readFileSync(sampleManifestPath, 'utf-8') }); 42 it(`adds the round icon property when an adaptive icon is present`, async () => { 43 const manifest = await readAndroidManifestAsync('./AndroidManifest.xml'); 44 const results = setRoundIconManifest({ android: { adaptiveIcon: {} } }, manifest); 45 46 const app = getMainApplicationOrThrow(results); 47 expect(app.$['android:roundIcon']).toBe('@mipmap/ic_launcher_round'); 48 }); 49 it(`removes the round icon property when an adaptive icon is missing`, async () => { 50 const manifest = await readAndroidManifestAsync('./AndroidManifest.xml'); 51 const results = setRoundIconManifest({ android: {} }, manifest); 52 53 const app = getMainApplicationOrThrow(results); 54 expect(app.$['android:roundIcon']).not.toBeDefined(); 55 }); 56}); 57 58describe('Android Icon', () => { 59 it(`returns null if no icon values provided`, () => { 60 expect(getIcon({} as ExpoConfig)).toBeNull(); 61 expect(getAdaptiveIcon({} as ExpoConfig)).toMatchObject({ 62 foregroundImage: null, 63 backgroundColor: null, 64 backgroundImage: null, 65 }); 66 }); 67 68 it(`returns adaptive icon over android icon`, () => { 69 const config = { 70 icon: 'icon', 71 android: { 72 icon: 'androidIcon', 73 adaptiveIcon: { 74 foregroundImage: 'adaptiveIcon', 75 backgroundImage: 'backgroundImage', 76 backgroundColor: '#000000', 77 }, 78 }, 79 }; 80 const { foregroundImage, backgroundColor, backgroundImage } = getAdaptiveIcon( 81 config as ExpoConfig 82 ); 83 const icon = foregroundImage || getIcon(config as ExpoConfig); 84 expect(icon).toMatch('adaptiveIcon'); 85 expect(backgroundColor).toMatch('#000000'); 86 expect(backgroundImage).toMatch('backgroundImage'); 87 }); 88 89 it(`creates the proper AdaptiveIconXmlString`, () => { 90 const withBackgroundImage = createAdaptiveIconXmlString('path/to/image'); 91 const withBackgroundColor = createAdaptiveIconXmlString(null); 92 const withBoth = createAdaptiveIconXmlString('path/to/image'); 93 94 expect(withBackgroundColor).toBe(ADAPTIVE_ICON_XML_WITH_BACKGROUND_COLOR); 95 expect(withBackgroundImage).toBe(ADAPTIVE_ICON_XML_WITH_BOTH); 96 expect(withBoth).toBe(ADAPTIVE_ICON_XML_WITH_BOTH); 97 }); 98 99 it('returns null if no icon config provided', async () => { 100 expect( 101 await setIconAsync('./', { 102 icon: null, 103 backgroundImage: null, 104 backgroundColor: null, 105 isAdaptive: false, 106 }) 107 ).toBe(null); 108 }); 109}); 110 111describe('e2e: ONLY android legacy icon', () => { 112 const legacyIconPath = path.resolve(__dirname, '../../__tests__/fixtures/icon.png'); 113 const projectRoot = '/app'; 114 const icon = require('../../icons/withAndroidIcons'); 115 const spyOnConfigureAdaptiveIconAsync = jest.spyOn(icon, 'configureAdaptiveIconAsync'); 116 beforeAll(async () => { 117 const icon = fsReal.readFileSync(legacyIconPath); 118 vol.fromJSON( 119 { './android/app/src/main/res/values/colors.xml': SAMPLE_COLORS_XML }, 120 projectRoot 121 ); 122 setUpMipmapDirectories(); 123 vol.mkdirpSync('/app/assets'); 124 vol.writeFileSync('/app/assets/iconForeground.png', icon); 125 126 await setIconAsync(projectRoot, { 127 icon: '/app/assets/iconForeground.png', 128 backgroundColor: null, 129 backgroundImage: null, 130 isAdaptive: true, 131 }); 132 }); 133 134 afterAll(() => { 135 vol.reset(); 136 }); 137 138 it('writes all the image files expected', async () => { 139 const after = getDirFromFS(vol.toJSON(), projectRoot); 140 Object.keys(after).forEach((path) => { 141 expect(LIST_OF_ANDROID_ADAPTIVE_ICON_FILES_FINAL).toContain(path); 142 }); 143 }); 144 145 it('Does not set adaptive icon config', () => { 146 expect(spyOnConfigureAdaptiveIconAsync).toHaveBeenCalledTimes(0); 147 }); 148}); 149 150describe('e2e: android adaptive icon', () => { 151 const adaptiveIconForegroundPath = path.resolve(__dirname, '../../__tests__/fixtures/icon.png'); 152 const adaptiveIconBackgroundPath = path.resolve(__dirname, '../../__tests__/fixtures/icon.png'); 153 const projectRoot = '/app'; 154 155 beforeAll(async () => { 156 const adaptiveIconForeground = fsReal.readFileSync(adaptiveIconForegroundPath); 157 const adaptiveIconBackground = fsReal.readFileSync(adaptiveIconBackgroundPath); 158 159 vol.fromJSON( 160 { './android/app/src/main/res/values/colors.xml': SAMPLE_COLORS_XML }, 161 projectRoot 162 ); 163 setUpMipmapDirectories(); 164 vol.mkdirpSync('/app/assets'); 165 vol.writeFileSync('/app/assets/iconForeground.png', adaptiveIconForeground); 166 vol.writeFileSync('/app/assets/iconBackground.png', adaptiveIconBackground); 167 168 await setIconAsync(projectRoot, { 169 icon: '/app/assets/iconForeground.png', 170 backgroundImage: '/app/assets/iconBackground.png', 171 backgroundColor: '#123456', 172 isAdaptive: true, 173 }); 174 }); 175 176 afterAll(() => { 177 vol.reset(); 178 }); 179 180 it('writes all the image files expected', () => { 181 const after = getDirFromFS(vol.toJSON(), projectRoot); 182 expect(Object.keys(after)).toEqual(LIST_OF_ANDROID_ADAPTIVE_ICON_FILES_FINAL); 183 }); 184}); 185