diff --git a/packages/ada_chat_flutter/lib/src/ada_web_view.dart b/packages/ada_chat_flutter/lib/src/ada_web_view.dart index b5c637cf..5bb48e1d 100644 --- a/packages/ada_chat_flutter/lib/src/ada_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/ada_web_view.dart @@ -181,7 +181,7 @@ class AdaWebViewState extends State { onPageFinished: onPageFinished, onHttpError: _onHttpError, onWebResourceError: _onWebResourceError, - onNavigationRequest: _onNavigationRequest, + onNavigationRequest: onNavigationRequest, ), ); @@ -191,12 +191,13 @@ class AdaWebViewState extends State { void _onConsoleMessage(JavaScriptConsoleMessage message) => widget.onConsoleMessage?.call(message.level.toString(), message.message); - FutureOr _onNavigationRequest(NavigationRequest request) { + @visibleForTesting + FutureOr onNavigationRequest(NavigationRequest request) { final uri = Uri.parse(request.url); log('AdaWebView:onNavigationRequest: ' 'url=${uri.toString()}, isMainFrame=${request.isMainFrame}'); - if (isInternalAdaUrl(uri)) { + if (isInternalAdaUrl(uri, widget.embedUri, widget.handle)) { return NavigationDecision.navigate; } @@ -215,16 +216,6 @@ class AdaWebViewState extends State { return NavigationDecision.prevent; } - @visibleForTesting - bool isInternalAdaUrl(Uri uri) => - _isAdaChatLink(uri) || _isAdaSupportLink(uri) || _isBlankPage(uri); - - bool _isBlankPage(Uri uri) => uri.toString() == 'about:blank'; - - bool _isAdaSupportLink(Uri uri) => uri.host == '${widget.handle}.ada.support'; - - bool _isAdaChatLink(Uri uri) => uri == widget.embedUri; - void _onWebResourceError(WebResourceError error) => log('AdaWebView:onWebResourceError: ' 'errorCode=${error.errorCode}, ' @@ -232,9 +223,7 @@ class AdaWebViewState extends State { void _onHttpError(HttpResponseError error) => widget.onLoadingError?.call( error.request?.uri.toString() ?? '', - 'uri=${error.response?.uri}, ' - 'statusCode=${error.response?.statusCode}, ' - 'headers=${error.response?.headers}, ', + 'statusCode=${error.response?.statusCode}', ); @visibleForTesting @@ -383,12 +372,4 @@ console.log("adaSettings: " + JSON.stringify(window.adaSettings)); }, ); } - - dynamic jsonStrToMap(String message) { - if (message.isEmpty) { - return {}; - } - - return jsonDecode(message); - } } diff --git a/packages/ada_chat_flutter/lib/src/utils.dart b/packages/ada_chat_flutter/lib/src/utils.dart index c0b8197e..027e8108 100644 --- a/packages/ada_chat_flutter/lib/src/utils.dart +++ b/packages/ada_chat_flutter/lib/src/utils.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/foundation.dart'; String get getOsName { @@ -9,3 +11,23 @@ String get getOsName { return 'N/A'; } } + +bool isInternalAdaUrl(Uri uri, Uri embedUri, String handle) => + isAdaChatLink(uri, embedUri) || + isAdaSupportLink(uri, handle) || + isBlankPage(uri); + +bool isBlankPage(Uri uri) => uri.toString() == 'about:blank'; + +bool isAdaSupportLink(Uri uri, String handle) => + uri.host == '$handle.ada.support'; + +bool isAdaChatLink(Uri uri, Uri embedUri) => uri == embedUri; + +dynamic jsonStrToMap(String message) { + if (message.isEmpty) { + return {}; + } + + return json.decode(message); +} diff --git a/packages/ada_chat_flutter/test/src/ada_web_view_test.dart b/packages/ada_chat_flutter/test/src/ada_web_view_test.dart index d01d733c..cbb693f0 100644 --- a/packages/ada_chat_flutter/test/src/ada_web_view_test.dart +++ b/packages/ada_chat_flutter/test/src/ada_web_view_test.dart @@ -1,6 +1,7 @@ import 'package:ada_chat_flutter/src/ada_controller.dart'; import 'package:ada_chat_flutter/src/ada_web_view.dart'; import 'package:ada_chat_flutter/src/browser_settings.dart'; +import 'package:ada_chat_flutter/src/customized_web_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -15,6 +16,23 @@ const _regexp = '_regexp'; const _handle = '_handle'; const _embedUri = 'https://example.com/embed.html'; +final _browserSettings = BrowserSettings( + pageBuilder: ( + context, + browser, + controller, + ) => + Column( + children: [ + Text(_title), + browser, + ], + ), + adaHideUrls: [ + RegExp(_regexp), + ], +); + void main() { late _MockAdaController mockAdaController; @@ -44,30 +62,6 @@ void main() { }, ); - testWidgets( - 'GIVEN the widget is pumped ' - 'WHEN isInternalAdaUrl is called ' - 'THEN should rebuild correct result', - (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: _buildAdaWebView(mockAdaController), - ), - ); - - final state = tester.state(find.byType(AdaWebView)); - - expect(state.isInternalAdaUrl(Uri.parse('about:blank')), true); - expect( - state.isInternalAdaUrl(Uri.parse('https://$_handle.ada.support/asd')), - true, - ); - expect(state.isInternalAdaUrl(Uri.parse(_embedUri)), true); - - expect(state.isInternalAdaUrl(Uri.parse('google.com')), false); - }, - ); - testWidgets( 'GIVEN the widget is pumped ' 'WHEN onPageFinished is called ' @@ -108,6 +102,38 @@ void main() { }); }, ); + + testWidgets( + 'GIVEN the widget is pumped ' + 'WHEN onNavigationRequest is called for external page ' + 'THEN should show customized webview', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: _buildAdaWebView(mockAdaController), + ), + ); + + final state = tester.state(find.byType(AdaWebView)); + + state.onNavigationRequest( + NavigationRequest( + url: 'https://external-page.com/index.html', + isMainFrame: true, + ), + ); + await tester.pumpAndSettle(); + + expect(find.text(_title), findsOneWidget); + expect( + find.byWidgetPredicate( + (w) => + w is CustomizedWebView && w.browserSettings == _browserSettings, + ), + findsOneWidget, + ); + }, + ); }); } @@ -119,21 +145,6 @@ Widget _buildAdaWebView(_MockAdaController mockAdaController) { rolloutOverride: 1, language: 'en', metaFields: const {'key': 'value'}, - browserSettings: BrowserSettings( - pageBuilder: ( - context, - browser, - controller, - ) => - Column( - children: [ - Text(_title), - browser, - ], - ), - adaHideUrls: [ - RegExp(_regexp), - ], - ), + browserSettings: _browserSettings, ); } diff --git a/packages/ada_chat_flutter/test/src/utils_test.dart b/packages/ada_chat_flutter/test/src/utils_test.dart index 248fa672..dc0014f3 100644 --- a/packages/ada_chat_flutter/test/src/utils_test.dart +++ b/packages/ada_chat_flutter/test/src/utils_test.dart @@ -31,5 +31,82 @@ void main() { expect(getOsName, equals('N/A')); }); }); + + group('isBlankPage tests - ', () { + test('isBlankPage returns true for about:blank', () { + expect(isBlankPage(Uri.parse('about:blank')), isTrue); + }); + + test('isBlankPage returns false for other URLs', () { + expect(isBlankPage(Uri.parse('https://example.com')), isFalse); + }); + }); + + group('isAdaSupportLink tests - ', () { + const handle = 'test-handle'; + + test('isAdaSupportLink returns true for valid Ada support links', () { + expect( + isAdaSupportLink( + Uri.parse('https://test-handle.ada.support'), handle), + isTrue); + }); + + test('isAdaSupportLink returns false for invalid Ada support links', () { + expect(isAdaSupportLink(Uri.parse('https://example.com'), handle), + isFalse); + }); + }); + + group('isAdaChatLink tests - ', () { + final embedUri = Uri.parse('https://example.com/embed.html'); + + test('isAdaChatLink returns true for matching embed URIs', () { + expect(isAdaChatLink(embedUri, embedUri), isTrue); + }); + + test('isAdaChatLink returns false for non-matching embed URIs', () { + expect( + isAdaChatLink(Uri.parse('https://example.com'), embedUri), isFalse); + }); + }); + + group('isInternalAdaUrl tests - ', () { + final embedUri = Uri.parse('https://example.com/embed.html'); + const handle = 'test-handle'; + + test('isInternalAdaUrl returns true for valid internal Ada URLs', () { + expect(isInternalAdaUrl(embedUri, embedUri, handle), isTrue); + expect( + isInternalAdaUrl( + Uri.parse('https://test-handle.ada.support'), embedUri, handle), + isTrue); + expect(isInternalAdaUrl(Uri.parse('about:blank'), embedUri, handle), + isTrue); + }); + + test('isInternalAdaUrl returns false for invalid internal Ada URLs', () { + expect( + isInternalAdaUrl( + Uri.parse('https://example.com'), embedUri, handle), + isFalse); + }); + }); + + group('jsonStrToMap', () { + test('returns empty map for empty string', () { + expect(jsonStrToMap(''), isEmpty); + expect(jsonStrToMap(''), isA>()); + }); + + test('decodes valid JSON string to map', () { + const jsonString = '{"key1": "value1", "key2": 123}'; + final result = jsonStrToMap(jsonString); + + expect(result, isA>()); + expect(result['key1'], equals('value1')); + expect(result['key2'], equals(123)); + }); + }); }); }