mirror of
https://github.com/selfxyz/self.git
synced 2026-04-05 03:00:53 -04:00
SELF-908: Add shared WebView screen (#1288)
* feat: add shared webview screen * get react web view working * fix default * fix footer * fix nav header * android layout looks good * fix initial screen * add webview types * fix types and clean error message * remove share logic * cr feedback and tests * fix tests * fix tests
This commit is contained in:
@@ -52,6 +52,7 @@ describe('navigation', () => {
|
||||
'Settings',
|
||||
'ShowRecoveryPhrase',
|
||||
'Splash',
|
||||
'WebView',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
137
app/tests/src/screens/WebViewScreen.test.tsx
Normal file
137
app/tests/src/screens/WebViewScreen.test.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import React from 'react';
|
||||
import { Linking } from 'react-native';
|
||||
import { render, screen, waitFor } from '@testing-library/react-native';
|
||||
|
||||
import { WebViewScreen } from '@/screens/shared/WebViewScreen';
|
||||
|
||||
jest.mock('react-native-webview', () => {
|
||||
const React = require('react');
|
||||
const { View } = require('react-native');
|
||||
const MockWebView = React.forwardRef((props: any, _ref) => {
|
||||
return React.createElement(View, { testID: 'webview', ...props });
|
||||
});
|
||||
MockWebView.displayName = 'MockWebView';
|
||||
return {
|
||||
__esModule: true,
|
||||
default: MockWebView,
|
||||
WebView: MockWebView,
|
||||
};
|
||||
});
|
||||
|
||||
describe('WebViewScreen URL sanitization and navigation interception', () => {
|
||||
const createProps = (initialUrl?: string, title?: string) => {
|
||||
return {
|
||||
navigation: {
|
||||
goBack: jest.fn(),
|
||||
canGoBack: jest.fn(() => true),
|
||||
} as any,
|
||||
route: {
|
||||
key: 'WebView-1',
|
||||
name: 'WebView',
|
||||
params: initialUrl
|
||||
? { url: initialUrl, title }
|
||||
: { url: 'https://self.xyz', title },
|
||||
} as any,
|
||||
};
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
(console.error as jest.Mock).mockRestore?.();
|
||||
});
|
||||
|
||||
it('sanitizes initial non-http(s) url and uses default', () => {
|
||||
render(<WebViewScreen {...createProps('intent://foo')} />);
|
||||
const webview = screen.getByTestId('webview');
|
||||
expect(webview.props.source).toEqual({ uri: 'https://self.xyz' });
|
||||
|
||||
// Title falls back to currentUrl (uppercase via NavBar), i.e., defaultUrl
|
||||
// We can't easily select NavBar text here without its internals; instead,
|
||||
// verify current source reflects the defaultUrl which the title derives from
|
||||
});
|
||||
|
||||
it('keeps currentUrl unchanged on non-http(s) navigation update', () => {
|
||||
render(<WebViewScreen {...createProps('http://example.com')} />);
|
||||
const webview = screen.getByTestId('webview');
|
||||
// simulate a navigation update with disallowed scheme
|
||||
webview.props.onNavigationStateChange?.({
|
||||
url: 'intent://foo',
|
||||
canGoBack: true,
|
||||
canGoForward: false,
|
||||
navigationType: 'other',
|
||||
title: undefined,
|
||||
});
|
||||
// Source remains the initial http URL since non-http(s) updates are ignored for currentUrl
|
||||
expect(webview.props.source).toEqual({ uri: 'http://example.com' });
|
||||
});
|
||||
|
||||
it('allows http(s) navigation via onShouldStartLoadWithRequest', () => {
|
||||
render(<WebViewScreen {...createProps('https://example.com')} />);
|
||||
const webview = screen.getByTestId('webview');
|
||||
const allowed = webview.props.onShouldStartLoadWithRequest?.({
|
||||
url: 'https://example.org',
|
||||
});
|
||||
expect(allowed).toBe(true);
|
||||
});
|
||||
|
||||
it('opens allowed external schemes externally and blocks in WebView (mailto, tel)', async () => {
|
||||
jest.spyOn(Linking, 'canOpenURL').mockResolvedValue(true as any);
|
||||
const openSpy = jest
|
||||
.spyOn(Linking, 'openURL')
|
||||
.mockResolvedValue(undefined as any);
|
||||
render(<WebViewScreen {...createProps('https://self.xyz')} />);
|
||||
const webview = screen.getByTestId('webview');
|
||||
|
||||
const resultMailto = await webview.props.onShouldStartLoadWithRequest?.({
|
||||
url: 'mailto:test@example.com',
|
||||
});
|
||||
expect(resultMailto).toBe(false);
|
||||
await waitFor(() =>
|
||||
expect(openSpy).toHaveBeenCalledWith('mailto:test@example.com'),
|
||||
);
|
||||
|
||||
const resultTel = await webview.props.onShouldStartLoadWithRequest?.({
|
||||
url: 'tel:+123456789',
|
||||
});
|
||||
expect(resultTel).toBe(false);
|
||||
await waitFor(() => expect(openSpy).toHaveBeenCalledWith('tel:+123456789'));
|
||||
});
|
||||
|
||||
it('blocks disallowed external schemes and does not attempt to open', async () => {
|
||||
const canOpenSpy = jest.spyOn(Linking, 'canOpenURL');
|
||||
const openSpy = jest.spyOn(Linking, 'openURL');
|
||||
render(<WebViewScreen {...createProps('https://self.xyz')} />);
|
||||
const webview = screen.getByTestId('webview');
|
||||
|
||||
const result = await webview.props.onShouldStartLoadWithRequest?.({
|
||||
url: 'ftp://example.com',
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
expect(canOpenSpy).not.toHaveBeenCalled();
|
||||
expect(openSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('scrubs error log wording when external open fails', async () => {
|
||||
jest.spyOn(Linking, 'canOpenURL').mockResolvedValue(true as any);
|
||||
jest.spyOn(Linking, 'openURL').mockRejectedValue(new Error('boom'));
|
||||
render(<WebViewScreen {...createProps('https://self.xyz')} />);
|
||||
const webview = screen.getByTestId('webview');
|
||||
|
||||
const result = await webview.props.onShouldStartLoadWithRequest?.({
|
||||
url: 'mailto:test@example.com',
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
await waitFor(() => expect(console.error).toHaveBeenCalled());
|
||||
const [msg] = (console.error as jest.Mock).mock.calls[0];
|
||||
expect(String(msg)).toContain('Failed to open externally');
|
||||
expect(String(msg)).not.toMatch(/Failed to open URL externally/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user