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