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