Repository navigation
Detect Dart web apps via dart:js_interop libraries #10045
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| // Copyright 2026 The Flutter Authors | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. | ||
|
|
||
| import 'package:devtools_app_shared/service.dart'; | ||
| import 'package:flutter_test/flutter_test.dart'; | ||
|
|
||
| void main() { | ||
| group('ConnectedApp', () { | ||
| test('isDartWebApp is true when dart:html is available', () async { | ||
| final serviceManager = _FakeServiceManager( | ||
| availableLibraries: ['dart:html'], | ||
| ); | ||
| final app = ConnectedApp(serviceManager); | ||
| expect(await app.isDartWebApp, isTrue); | ||
| expect(app.isDartWebAppNow, isTrue); | ||
| }); | ||
|
|
||
| test('isDartWebApp is true when dart:js_interop is available', () async { | ||
| final serviceManager = _FakeServiceManager( | ||
| availableLibraries: ['dart:js_interop'], | ||
| ); | ||
| final app = ConnectedApp(serviceManager); | ||
| expect(await app.isDartWebApp, isTrue); | ||
| expect(app.isDartWebAppNow, isTrue); | ||
| }); | ||
|
|
||
| test( | ||
| 'isDartWebApp is true when dart:js_interop_unsafe is available', | ||
| () async { | ||
| final serviceManager = _FakeServiceManager( | ||
| availableLibraries: ['dart:js_interop_unsafe'], | ||
| ); | ||
| final app = ConnectedApp(serviceManager); | ||
| expect(await app.isDartWebApp, isTrue); | ||
| expect(app.isDartWebAppNow, isTrue); | ||
| }, | ||
| ); | ||
|
Comment on lines
+28
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete this test. It passes only because the |
||
|
|
||
| test('isDartWebApp is false when no web libraries are available', () async { | ||
| final serviceManager = _FakeServiceManager( | ||
| availableLibraries: [ | ||
| 'dart:io', | ||
| 'package:flutter/src/widgets/binding.dart', | ||
| ], | ||
| ); | ||
| final app = ConnectedApp(serviceManager); | ||
| expect(await app.isDartWebApp, isFalse); | ||
| expect(app.isDartWebAppNow, isFalse); | ||
| }); | ||
|
|
||
| test('isDartWebApp short-circuits on first matching library', () async { | ||
| final queriedLibraries = <String>[]; | ||
| final serviceManager = _FakeServiceManager( | ||
| availableLibraries: ['dart:html', 'dart:js_interop'], | ||
| onLibraryUriAvailable: queriedLibraries.add, | ||
| ); | ||
| final app = ConnectedApp(serviceManager); | ||
|
|
||
| expect(await app.isDartWebApp, isTrue); | ||
| expect(queriedLibraries, equals(['dart:html'])); | ||
| }); | ||
|
|
||
| test('isDartWebApp caches results', () async { | ||
| var callCount = 0; | ||
| final serviceManager = _FakeServiceManager( | ||
| availableLibraries: ['dart:js_interop'], | ||
| onLibraryUriAvailable: (_) => callCount++, | ||
| ); | ||
| final app = ConnectedApp(serviceManager); | ||
|
|
||
| expect(await app.isDartWebApp, isTrue); | ||
| final callsAfterFirst = callCount; | ||
| expect(callsAfterFirst, greaterThan(0)); | ||
|
|
||
| expect(await app.isDartWebApp, isTrue); | ||
| expect(callCount, equals(callsAfterFirst)); | ||
| }); | ||
|
|
||
| test('isDartWebAppNow asserts when not initialized', () { | ||
| final serviceManager = _FakeServiceManager(); | ||
| final app = ConnectedApp(serviceManager); | ||
| expect(() => app.isDartWebAppNow, throwsAssertionError); | ||
| }); | ||
|
|
||
| test( | ||
| 'isFlutterWebAppNow is true and isFlutterNativeAppNow is false for modern Flutter web apps', | ||
| () async { | ||
| final serviceManager = _FakeServiceManager( | ||
| availableLibraries: [flutterLibraryUri, 'dart:js_interop'], | ||
| ); | ||
| final app = ConnectedApp(serviceManager); | ||
| expect(await app.isFlutterApp, isTrue); | ||
| expect(await app.isDartWebApp, isTrue); | ||
| expect(app.isFlutterWebAppNow, isTrue); | ||
| expect(app.isFlutterNativeAppNow, isFalse); | ||
| }, | ||
| ); | ||
| }); | ||
|
|
||
| group('OfflineConnectedApp', () { | ||
| test( | ||
| 'isDartWebApp returns the recorded value without a service manager', | ||
| () async { | ||
| expect( | ||
| await OfflineConnectedApp(isDartWebAppNow: true).isDartWebApp, | ||
| isTrue, | ||
| ); | ||
| expect( | ||
| await OfflineConnectedApp(isDartWebAppNow: false).isDartWebApp, | ||
| isFalse, | ||
| ); | ||
| expect(await OfflineConnectedApp().isDartWebApp, isFalse); | ||
| }, | ||
| ); | ||
| }); | ||
|
|
||
| group('constants', () { | ||
| test('dartWebLibraryUris contains all web library URIs', () { | ||
| expect( | ||
| dartWebLibraryUris, | ||
| containsAll([ | ||
| dartHtmlLibraryUri, | ||
| dartJsInteropLibraryUri, | ||
| dartJsInteropUnsafeLibraryUri, | ||
| ]), | ||
| ); | ||
| }); | ||
| }); | ||
|
Comment on lines
+118
to
+129
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete this group. It just restates the const list and can't catch a regression. |
||
| } | ||
|
|
||
| class _FakeServiceManager extends Fake implements ServiceManager { | ||
| _FakeServiceManager({ | ||
| this.availableLibraries = const [], | ||
| this.onLibraryUriAvailable, | ||
| }); | ||
|
|
||
| final List<String> availableLibraries; | ||
| final void Function(String uri)? onLibraryUriAvailable; | ||
|
|
||
| @override | ||
| bool libraryUriAvailableNow(String? uri) { | ||
| if (uri == null) return false; | ||
| onLibraryUriAvailable?.call(uri); | ||
| return availableLibraries.any((u) => u.startsWith(uri)); | ||
| } | ||
|
|
||
| @override | ||
| Future<bool> libraryUriAvailable(String uri) async { | ||
| return libraryUriAvailableNow(uri); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -234,6 +234,11 @@ class FakeServiceManager extends Fake | |
| return availableLibraries.any((u) => u.startsWith(uri)); | ||
| } | ||
|
|
||
| @override | ||
| Future<bool> libraryUriAvailable(String uri) async { | ||
| return libraryUriAvailableNow(uri); | ||
| } | ||
|
Comment on lines
+237
to
+240
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Drop this unless a test needs it. Nothing in this PR calls |
||
|
|
||
| @override | ||
| Future<void> manuallyDisconnect() async { | ||
| setConnectedState(false, manual: true); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove
dartJsInteropUnsafeLibraryUriand its entry indartWebLibraryUris.libraryUriAvailableNowmatches withstartsWith, so'dart:js_interop'already matchesdart:js_interop_unsafe. Also,dart:js_interop_unsafeimportsdart:js_interop, so it is never loaded on its own. Update the CHANGELOG entry to match.