1import { ExpoConfig } from 'expo/config';
2import { fs, vol } from 'memfs';
3import * as path from 'path';
4
5import {
6  getNotificationColor,
7  getNotificationIcon,
8  setNotificationIconAsync,
9  setNotificationSounds,
10} from '../withNotificationsAndroid';
11
12export function getDirFromFS(fsJSON: Record<string, string | null>, rootDir: string) {
13  return Object.entries(fsJSON)
14    .filter(([path, value]) => value !== null && path.startsWith(rootDir))
15    .reduce<Record<string, string>>(
16      (acc, [path, fileContent]) => ({
17        ...acc,
18        [path.substring(rootDir.length).startsWith('/')
19          ? path.substring(rootDir.length + 1)
20          : path.substring(rootDir.length)]: fileContent,
21      }),
22      {}
23    );
24}
25
26const SAMPLE_COLORS_XML = `<?xml version="1.0" encoding="utf-8"?>
27    <resources>
28      <!-- Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually -->
29      <color name="splashscreen_background">#FFFFFF</color>
30    </resources>
31    `;
32
33jest.mock('fs');
34
35const fsReal = jest.requireActual('fs') as typeof fs;
36
37const LIST_OF_GENERATED_NOTIFICATION_FILES = [
38  'android/app/src/main/res/drawable-mdpi/notification_icon.png',
39  'android/app/src/main/res/drawable-hdpi/notification_icon.png',
40  'android/app/src/main/res/drawable-xhdpi/notification_icon.png',
41  'android/app/src/main/res/drawable-xxhdpi/notification_icon.png',
42  'android/app/src/main/res/drawable-xxxhdpi/notification_icon.png',
43  'android/app/src/main/res/values/colors.xml',
44  'assets/notificationIcon.png',
45  'assets/notificationSound.wav',
46  'android/app/src/main/res/raw/notificationSound.wav',
47];
48
49const iconPath = path.resolve(__dirname, './fixtures/icon.png');
50const soundPath = path.resolve(__dirname, './fixtures/cat.wav');
51
52const projectRoot = '/app';
53
54describe('Android notifications configuration', () => {
55  beforeEach(async () => {
56    const icon = fsReal.readFileSync(iconPath);
57    const sound = fsReal.readFileSync(soundPath);
58    vol.fromJSON(
59      { './android/app/src/main/res/values/colors.xml': SAMPLE_COLORS_XML },
60      projectRoot
61    );
62    setUpDrawableDirectories();
63    vol.mkdirpSync('/app/assets');
64    vol.writeFileSync('/app/assets/notificationIcon.png', icon);
65    vol.writeFileSync('/app/assets/notificationSound.wav', sound);
66  });
67
68  afterEach(() => {
69    vol.reset();
70  });
71
72  afterAll(() => {
73    jest.unmock('@expo/image-utils');
74    jest.unmock('fs');
75  });
76
77  it(`returns null if no config provided`, () => {
78    expect(getNotificationIcon({} as ExpoConfig)).toBeNull();
79    expect(getNotificationColor({} as ExpoConfig)).toBeNull();
80  });
81
82  it(`returns config if provided`, () => {
83    expect(getNotificationIcon({ notification: { icon: './myIcon.png' } } as ExpoConfig)).toMatch(
84      './myIcon.png'
85    );
86    expect(getNotificationColor({ notification: { color: '#123456' } } as ExpoConfig)).toMatch(
87      '#123456'
88    );
89  });
90
91  it('writes all the asset files (sounds and images) as expected', async () => {
92    await setNotificationIconAsync(projectRoot, '/app/assets/notificationIcon.png');
93    setNotificationSounds(projectRoot, ['/app/assets/notificationSound.wav']);
94
95    const after = getDirFromFS(vol.toJSON(), projectRoot);
96    expect(Object.keys(after).sort()).toEqual(LIST_OF_GENERATED_NOTIFICATION_FILES.sort());
97  });
98
99  it('Safely remove icon if it exists, and ignore if it doesnt', async () => {
100    const before = getDirFromFS(vol.toJSON(), projectRoot);
101    // first set the icon
102    await setNotificationIconAsync(projectRoot, '/app/assets/notificationIcon.png');
103
104    // now remove
105    await setNotificationIconAsync(projectRoot, null);
106
107    const after = getDirFromFS(vol.toJSON(), projectRoot);
108    expect(before).toMatchObject(after);
109
110    // now remove again to make sure we don't throw in that case
111    await setNotificationIconAsync(projectRoot, null);
112
113    const final = getDirFromFS(vol.toJSON(), projectRoot);
114    expect(before).toMatchObject(final);
115  });
116});
117
118function setUpDrawableDirectories() {
119  vol.mkdirpSync('/app/android/app/src/main/res/drawable-mdpi');
120  vol.mkdirpSync('/app/android/app/src/main/res/drawable-hdpi');
121  vol.mkdirpSync('/app/android/app/src/main/res/drawable-xhdpi');
122  vol.mkdirpSync('/app/android/app/src/main/res/drawable-xxhdpi');
123  vol.mkdirpSync('/app/android/app/src/main/res/drawable-xxxhdpi');
124}
125