Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/material_ui/lib/src/_about_io.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:io' show Platform;

/// The file name of the currently running executable.
///
/// Used as the fallback application name by the about dialog widgets when
/// no `Title` ancestor is available.
String get executableName => Platform.resolvedExecutable.split(Platform.pathSeparator).last;
8 changes: 8 additions & 0 deletions packages/material_ui/lib/src/_about_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/// The file name of the currently running executable.
///
/// There is no executable on the web, so this is always the empty string.
String get executableName => '';
6 changes: 4 additions & 2 deletions packages/material_ui/lib/src/about.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/// @docImport 'dart:io';
///
/// @docImport 'package:cupertino_ui/cupertino_ui.dart';
///
/// @docImport 'drawer.dart';
/// @docImport 'list_tile_theme.dart';
library;

import 'dart:developer' show Flow, Timeline;
import 'dart:io' show Platform;

import 'package:cupertino_ui/cupertino_ui.dart' show CupertinoDialogAction;
import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart' hide Flow;

import '_about_io.dart' if (dart.library.js_interop) '_about_web.dart' as about;
import 'app_bar.dart';
import 'back_button.dart';
import 'card.dart';
Expand Down Expand Up @@ -1201,7 +1203,7 @@ String _defaultApplicationName(BuildContext context) {
// can provide an explicit applicationName to the widgets defined in this
// file, instead of relying on the default.
final Title? ancestorTitle = context.findAncestorWidgetOfExactType<Title>();
return ancestorTitle?.title ?? Platform.resolvedExecutable.split(Platform.pathSeparator).last;
return ancestorTitle?.title ?? about.executableName;
}

String _defaultApplicationVersion(BuildContext context) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changelog: |
- Removes the unconditional `dart:io` import from `AboutDialog`, so that `material_ui` is correctly detected as supporting the web platform and `showAboutDialog` no longer throws on the web when no application name is available.
version: patch
42 changes: 42 additions & 0 deletions packages/material_ui/test/about_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import 'dart:async';
import 'dart:io' show Platform;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Unconditionally importing dart:io in a test file will cause compilation failures when running tests on the web (e.g., via flutter test --platform chrome). Since this PR aims to enable web support, we should avoid unconditional dart:io imports in the test file as well. We can use a conditional import to import the platform-specific executable name helper instead.

Suggested change
import 'dart:io' show Platform;
import 'package:material_ui/src/_about_io.dart'
if (dart.library.js_interop) 'package:material_ui/src/_about_web.dart' as about;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dart:io compiles on the web (it just throws at runtime), so an unconditional import in a test file does not break web test compilation. This file passes with flutter test --platform chrome as-is, and the only test that touches Platform is skipped on the web with skip: kIsWeb. Other tests in this repo (e.g. test/l10n/translations_test.dart) import dart:io the same way. Importing src/_about_io.dart from a test would also couple the test to a private implementation file, so I would prefer to keep the plain import.

import 'dart:ui';

import 'package:cupertino_ui/cupertino_ui.dart';
Expand All @@ -16,6 +17,47 @@ void main() {
LicenseRegistry.reset();
});

Widget buildWithoutTitle(Widget child) {
return MediaQuery(
data: const MediaQueryData(),
child: Localizations(
locale: const Locale('en', 'US'),
delegates: const <LocalizationsDelegate<dynamic>>[
DefaultMaterialLocalizations.delegate,
DefaultWidgetsLocalizations.delegate,
],
child: Directionality(
textDirection: TextDirection.ltr,
child: Theme(data: ThemeData(), child: child),
),
),
);
}

testWidgets(
'AboutDialog defaults the application name to the executable name without a Title ancestor',
(WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/191887.
await tester.pumpWidget(buildWithoutTitle(const AboutDialog()));

final String expectedName = Platform.resolvedExecutable.split(Platform.pathSeparator).last;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Use the conditionally imported about.executableName instead of directly accessing Platform.resolvedExecutable to avoid the compile-time dependency on dart:io on the web.

Suggested change
final String expectedName = Platform.resolvedExecutable.split(Platform.pathSeparator).last;
final String expectedName = about.executableName;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Computing the expected value from Platform.resolvedExecutable directly is intentional: the widget already reads about.executableName, so using the same getter as the expected value would just compare the getter against itself. Deriving it independently keeps the test meaningful. This test is skipped on the web (skip: kIsWeb), so there is no dart:io runtime dependency there.

expect(find.text(expectedName), findsOneWidget);
},
skip: kIsWeb, // [intended] There is no executable name on the web.
);

testWidgets(
'AboutDialog does not throw on the web without a Title ancestor',
(WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/191887.
await tester.pumpWidget(buildWithoutTitle(const AboutDialog()));

expect(tester.takeException(), isNull);
expect(find.byType(AboutDialog), findsOneWidget);
},
skip: !kIsWeb, // [intended] Exercises the web-specific fallback.
);

testWidgets('Material3 has sentence case labels', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
Expand Down