1import { resolve } from 'path';
2
3import rnFixture from '../../plugins/__tests__/fixtures/react-native-project';
4import * as XML from '../../utils/XML';
5import { AndroidManifest, getMainActivity, readAndroidManifestAsync } from '../Manifest';
6import {
7  appendScheme,
8  ensureManifestHasValidIntentFilter,
9  getScheme,
10  getSchemesFromManifest,
11  hasScheme,
12  removeScheme,
13  setScheme,
14} from '../Scheme';
15
16async function getFixtureManifestAsync() {
17  return (await XML.parseXMLAsync(
18    rnFixture['android/app/src/main/AndroidManifest.xml']
19  )) as AndroidManifest;
20}
21
22const fixturesPath = resolve(__dirname, 'fixtures');
23const sampleManifestWithHostPath = resolve(
24  fixturesPath,
25  'react-native-AndroidManifest-with-host.xml'
26);
27
28describe('scheme', () => {
29  it(`returns empty array if no scheme is provided`, () => {
30    expect(getScheme({})).toStrictEqual([]);
31  });
32
33  it(`returns the scheme if provided`, () => {
34    expect(getScheme({ scheme: 'myapp' })).toStrictEqual(['myapp']);
35    expect(getScheme({ scheme: ['other', 'myapp'] })).toStrictEqual(['other', 'myapp']);
36    expect(
37      getScheme({
38        scheme: [
39          'other',
40          'myapp',
41          // @ts-expect-error
42          null,
43        ],
44      })
45    ).toStrictEqual(['other', 'myapp']);
46  });
47
48  it('does not add scheme if none provided', async () => {
49    let androidManifestJson = await getFixtureManifestAsync();
50    androidManifestJson = await setScheme({}, androidManifestJson);
51
52    expect(androidManifestJson).toEqual(androidManifestJson);
53  });
54
55  it('adds scheme to android manifest', async () => {
56    let androidManifestJson = await getFixtureManifestAsync();
57    androidManifestJson = await setScheme(
58      {
59        scheme: 'myapp',
60        android: {
61          // @ts-ignore
62          scheme: ['android-only'],
63          package: 'com.demo.value',
64        },
65        ios: { scheme: 'ios-only' },
66      },
67      androidManifestJson
68    );
69
70    const mainActivity = getMainActivity(androidManifestJson);
71    const intentFilters = mainActivity!['intent-filter']!;
72
73    const schemeIntent: string[] = [];
74
75    for (const intent of intentFilters) {
76      if ('data' in intent) {
77        for (const dataFilter of intent.data!) {
78          const possibleScheme = dataFilter.$['android:scheme'];
79          if (possibleScheme) {
80            schemeIntent.push(possibleScheme);
81          }
82        }
83      }
84    }
85
86    expect(schemeIntent).toStrictEqual(['myapp', 'android-only', 'com.demo.value']);
87  });
88});
89
90function removeSingleTaskFromActivities(manifest) {
91  for (const application of manifest.manifest.application) {
92    for (const activity of application.activity) {
93      if (activity.$['android:launchMode'] === 'singleTask') {
94        delete activity.$['android:launchMode'];
95      }
96    }
97  }
98
99  return manifest;
100}
101
102describe('Schemes', () => {
103  it(`ensure manifest has valid intent filter added`, async () => {
104    const manifest = await getFixtureManifestAsync();
105    const manifestHasValidIntentFilter = ensureManifestHasValidIntentFilter(manifest);
106    expect(manifestHasValidIntentFilter).toBe(true);
107  });
108
109  it(`detect if no singleTask Activity exists`, async () => {
110    const manifest = await getFixtureManifestAsync();
111    removeSingleTaskFromActivities(manifest);
112
113    expect(ensureManifestHasValidIntentFilter(manifest)).toBe(false);
114  });
115
116  it(`adds and removes a new scheme`, async () => {
117    const manifest = await getFixtureManifestAsync();
118    ensureManifestHasValidIntentFilter(manifest);
119
120    const modifiedManifest = appendScheme('myapp.test', manifest);
121    const schemes = getSchemesFromManifest(modifiedManifest);
122    expect(schemes).toContain('myapp.test');
123    const removedManifest = removeScheme('myapp.test', manifest);
124    expect(getSchemesFromManifest(removedManifest)).not.toContain('myapp.test');
125  });
126
127  it(`get all schemes for the host`, async () => {
128    const manifest = await readAndroidManifestAsync(sampleManifestWithHostPath);
129    ensureManifestHasValidIntentFilter(manifest);
130    expect(hasScheme('longestschemewiththehost', manifest)).toBe(true);
131
132    const modifiedManifest = appendScheme('myapp.test', manifest);
133    let schemes = getSchemesFromManifest(modifiedManifest, 'any-host');
134    expect(schemes).toContain('myapp.test');
135    expect(schemes).not.toContain('longestschemewiththehost');
136
137    schemes = getSchemesFromManifest(modifiedManifest, 'expo-development-client');
138    expect(schemes).toContain('myapp.test');
139    expect(schemes).toContain('longestschemewiththehost');
140  });
141
142  it(`detect when a duplicate might be added`, async () => {
143    const manifest = await getFixtureManifestAsync();
144    ensureManifestHasValidIntentFilter(manifest);
145
146    const modifiedManifest = appendScheme('myapp.test', manifest);
147    expect(hasScheme('myapp.test', modifiedManifest)).toBe(true);
148  });
149
150  it(`detect a non-existent scheme`, async () => {
151    const manifest = await getFixtureManifestAsync();
152
153    expect(hasScheme('myapp.test', manifest)).toBe(false);
154  });
155});
156