xref: /expo/apps/test-suite/tests/Linking.js (revision 665c2bac)
1import Constants from 'expo-constants';
2import * as Linking from 'expo-linking';
3import * as WebBrowser from 'expo-web-browser';
4import { LogBox, Platform } from 'react-native';
5
6import { waitFor } from './helpers';
7
8const validHttpUrl = 'http://expo.io/';
9const validHttpsUrl = 'https://expo.io/';
10const validExpUrl = 'exp://expo.io/@community/native-component-list';
11const redirectingBackendUrl = 'https://backend-xxswjknyfi.now.sh/?linkingUri=';
12
13// Because the root navigator of test-suite doesn't have a matching screen for URL, it will warn.
14// This is expected as all tests are wrapped in `screens/TestScreen.js`, and not defined as separate screens.
15LogBox.ignoreLogs([
16  'navigation state parsed from the URL contains routes not present in the root navigator',
17]);
18
19export const name = 'Linking';
20
21export function test(t) {
22  t.describe('Linking', () => {
23    t.describe('canOpenUrl', () => {
24      t.it('can open exp:// URLs', async () => {
25        t.expect(await Linking.canOpenURL(validExpUrl)).toBe(true);
26      });
27
28      t.it('can open its own URLs', async () => {
29        t.expect(await Linking.canOpenURL(Constants.linkingUri)).toBe(true);
30      });
31
32      t.it('can open http:// URLs', async () => {
33        t.expect(await Linking.canOpenURL(validHttpUrl)).toBe(true);
34      });
35
36      t.it('can open https:// URLs', async () => {
37        t.expect(await Linking.canOpenURL(validHttpsUrl)).toBe(true);
38      });
39    });
40
41    t.describe('addListener', () => {
42      let previousInterval = 0;
43      t.beforeAll(() => {
44        previousInterval = t.jasmine.DEFAULT_TIMEOUT_INTERVAL;
45        t.jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
46      });
47      t.afterAll(() => {
48        t.jasmine.DEFAULT_TIMEOUT_INTERVAL = previousInterval;
49      });
50
51      if (Platform.OS === 'android') {
52        // We can't run this test on iOS since iOS it fails with an exception
53        // "The specified URL has an unsupported scheme. Only HTTP and HTTPS URLs are supported."
54        t.it('listener gets called with a proper URL when opened from a web modal', async () => {
55          let handlerCalled = false;
56          const testUrl = Linking.makeUrl('++message=hello');
57          const handler = ({ url }) => {
58            t.expect(url).toEqual(testUrl);
59            handlerCalled = true;
60          };
61          Linking.addEventListener('url', handler);
62          await WebBrowser.openBrowserAsync(testUrl);
63          await waitFor(8000);
64          t.expect(handlerCalled).toBe(true);
65          Linking.removeEventListener('url', handler);
66        });
67
68        // We can't run this test on iOS since iOS asks "whether to open this link in Expo"
69        // and we can't programmatically tap "Open".
70        t.it('listener gets called with a proper URL when opened from a web browser', async () => {
71          let handlerCalled = false;
72          const handler = ({ url }) => {
73            t.expect(url).toEqual(Linking.makeUrl('++message=Redirected automatically by timer'));
74            handlerCalled = true;
75          };
76          Linking.addEventListener('url', handler);
77          await Linking.openURL(`${redirectingBackendUrl}${Linking.makeUrl('++')}`);
78          await waitFor(8000);
79          t.expect(handlerCalled).toBe(true);
80          Linking.removeEventListener('url', handler);
81        });
82      }
83
84      t.it('listener gets called with a proper URL when opened from a web modal', async () => {
85        let handlerCalled = false;
86        const handler = ({ url }) => {
87          t.expect(url).toEqual(Linking.makeUrl('++message=Redirected automatically by timer'));
88          handlerCalled = true;
89          if (Platform.OS === 'ios') WebBrowser.dismissBrowser();
90        };
91        Linking.addEventListener('url', handler);
92        await WebBrowser.openBrowserAsync(`${redirectingBackendUrl}${Linking.makeUrl('++')}`);
93        await waitFor(1000);
94        t.expect(handlerCalled).toBe(true);
95        Linking.removeEventListener('url', handler);
96      });
97
98      t.it('listener gets called with a proper URL when opened with Linking.openURL', async () => {
99        let handlerCalled = false;
100        const handler = ({ url }) => {
101          handlerCalled = true;
102        };
103        Linking.addEventListener('url', handler);
104        await Linking.openURL(Linking.makeUrl('++'));
105        await waitFor(500);
106        t.expect(handlerCalled).toBe(true);
107        Linking.removeEventListener('url', handler);
108      });
109
110      t.it('listener parses out deep link information correctly', async () => {
111        let handlerCalled = false;
112        const handler = ({ url }) => {
113          const { path, queryParams } = Linking.parse(url);
114          // ignore +'s on the front of path,
115          // since there may be one or two depending on how test-suite is being served
116          t.expect(path.replace(/\+/g, '')).toEqual('test/path');
117          t.expect(queryParams.query).toEqual('param');
118          handlerCalled = true;
119        };
120        Linking.addEventListener('url', handler);
121        await Linking.openURL(Linking.makeUrl('++test/path?query=param'));
122        await waitFor(500);
123        t.expect(handlerCalled).toBe(true);
124        Linking.removeEventListener('url', handler);
125      });
126    });
127  });
128}
129