xref: /expo/apps/test-suite/tests/Linking.js (revision 2f31cd71)
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://exp.host/';
9const validHttpsUrl = 'https://exp.host/';
10const validExpUrl = 'exp://exp.host/@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.createURL('++message=hello');
57          const handler = ({ url }) => {
58            t.expect(url).toEqual(testUrl);
59            handlerCalled = true;
60          };
61          const subscription = Linking.addEventListener('url', handler);
62          await WebBrowser.openBrowserAsync(testUrl);
63          await waitFor(8000);
64          t.expect(handlerCalled).toBe(true);
65          t.expect(subscription).toBeTruthy();
66          subscription.remove();
67        });
68
69        // We can't run this test on iOS since iOS asks "whether to open this link in Expo"
70        // and we can't programmatically tap "Open".
71        t.it('listener gets called with a proper URL when opened from a web browser', async () => {
72          let handlerCalled = false;
73          const handler = ({ url }) => {
74            t.expect(url).toEqual(Linking.createURL('++message=Redirected automatically by timer'));
75            handlerCalled = true;
76          };
77          const subscription = Linking.addEventListener('url', handler);
78          await Linking.openURL(`${redirectingBackendUrl}${Linking.createURL('++')}`);
79          await waitFor(8000);
80          t.expect(handlerCalled).toBe(true);
81          subscription.remove();
82        });
83      }
84
85      t.it('listener gets called with a proper URL when opened from a web modal', async () => {
86        let handlerCalled = false;
87        const handler = ({ url }) => {
88          t.expect(url).toEqual(Linking.createURL('++message=Redirected automatically by timer'));
89          handlerCalled = true;
90          if (Platform.OS === 'ios') WebBrowser.dismissBrowser();
91        };
92        const subscription = Linking.addEventListener('url', handler);
93        await WebBrowser.openBrowserAsync(`${redirectingBackendUrl}${Linking.createURL('++')}`);
94        await waitFor(1000);
95        t.expect(handlerCalled).toBe(true);
96        subscription.remove();
97      });
98
99      t.it('listener gets called with a proper URL when opened with Linking.openURL', async () => {
100        let handlerCalled = false;
101        const handler = ({ url }) => {
102          handlerCalled = true;
103        };
104        const subscription = Linking.addEventListener('url', handler);
105        await Linking.openURL(Linking.createURL('++'));
106        await waitFor(500);
107        t.expect(handlerCalled).toBe(true);
108        subscription.remove();
109      });
110
111      t.it('listener parses out deep link information correctly', async () => {
112        let handlerCalled = false;
113        const handler = ({ url }) => {
114          const { path, queryParams } = Linking.parse(url);
115          // ignore +'s on the front of path,
116          // since there may be one or two depending on how test-suite is being served
117          t.expect(path.replace(/\+/g, '')).toEqual('test/path');
118          t.expect(queryParams.query).toEqual('param');
119          handlerCalled = true;
120        };
121        const subscription = Linking.addEventListener('url', handler);
122        await Linking.openURL(Linking.createURL('++test/path?query=param'));
123        await waitFor(500);
124        t.expect(handlerCalled).toBe(true);
125        subscription.remove();
126      });
127    });
128  });
129}
130