mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
452 lines
14 KiB
Dart
452 lines
14 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// This file is run as part of a reduced test set in CI on Mac and Windows
|
|
// machines.
|
|
@Tags(<String>['reduced-test-set'])
|
|
library;
|
|
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
test('CardThemeData copyWith, ==, hashCode basics', () {
|
|
expect(const CardThemeData(), const CardThemeData().copyWith());
|
|
expect(const CardThemeData().hashCode, const CardThemeData().copyWith().hashCode);
|
|
});
|
|
|
|
test('CardThemeData lerp special cases', () {
|
|
expect(CardThemeData.lerp(null, null, 0), const CardThemeData());
|
|
const theme = CardThemeData();
|
|
expect(identical(CardThemeData.lerp(theme, theme, 0.5), theme), true);
|
|
});
|
|
|
|
test('CardThemeData defaults', () {
|
|
const cardThemeData = CardThemeData();
|
|
|
|
expect(cardThemeData.clipBehavior, null);
|
|
expect(cardThemeData.color, null);
|
|
expect(cardThemeData.elevation, null);
|
|
expect(cardThemeData.margin, null);
|
|
expect(cardThemeData.shadowColor, null);
|
|
expect(cardThemeData.shape, null);
|
|
expect(cardThemeData.surfaceTintColor, null);
|
|
|
|
const cardTheme = CardTheme(data: CardThemeData(), child: SizedBox());
|
|
expect(cardTheme.clipBehavior, null);
|
|
expect(cardTheme.color, null);
|
|
expect(cardTheme.elevation, null);
|
|
expect(cardTheme.margin, null);
|
|
expect(cardTheme.shadowColor, null);
|
|
expect(cardTheme.shape, null);
|
|
expect(cardTheme.surfaceTintColor, null);
|
|
});
|
|
|
|
testWidgets('Default CardThemeData debugFillProperties', (WidgetTester tester) async {
|
|
final builder = DiagnosticPropertiesBuilder();
|
|
const CardThemeData().debugFillProperties(builder);
|
|
|
|
final List<String> description = builder.properties
|
|
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
|
.map((DiagnosticsNode node) => node.toString())
|
|
.toList();
|
|
|
|
expect(description, <String>[]);
|
|
});
|
|
|
|
testWidgets('CardThemeData implements debugFillProperties', (WidgetTester tester) async {
|
|
final builder = DiagnosticPropertiesBuilder();
|
|
const CardThemeData(
|
|
clipBehavior: Clip.antiAlias,
|
|
color: Colors.amber,
|
|
elevation: 10.5,
|
|
margin: EdgeInsets.all(20.5),
|
|
shadowColor: Colors.green,
|
|
surfaceTintColor: Colors.purple,
|
|
shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20.5))),
|
|
).debugFillProperties(builder);
|
|
|
|
final List<String> description = builder.properties
|
|
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
|
.map((DiagnosticsNode node) => node.toString())
|
|
.toList();
|
|
|
|
expect(description[0], 'clipBehavior: Clip.antiAlias');
|
|
expect(description[1], 'color: MaterialColor(primary value: ${const Color(0xffffc107)})');
|
|
expect(description[2], 'shadowColor: MaterialColor(primary value: ${const Color(0xff4caf50)})');
|
|
expect(
|
|
description[3],
|
|
'surfaceTintColor: MaterialColor(primary value: ${const Color(0xff9c27b0)})',
|
|
);
|
|
expect(description[4], 'elevation: 10.5');
|
|
expect(description[5], 'margin: EdgeInsets.all(20.5)');
|
|
expect(
|
|
description[6],
|
|
'shape: BeveledRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.circular(20.5))',
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - Passing no CardTheme returns defaults', (WidgetTester tester) async {
|
|
final theme = ThemeData();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: const Scaffold(body: Card()),
|
|
),
|
|
);
|
|
|
|
final Padding padding = _getCardPadding(tester);
|
|
final Material material = _getCardMaterial(tester);
|
|
|
|
expect(material.clipBehavior, Clip.none);
|
|
expect(material.color, theme.colorScheme.surfaceContainerLow);
|
|
expect(material.shadowColor, theme.colorScheme.shadow);
|
|
expect(material.surfaceTintColor, Colors.transparent); // Default primary color
|
|
expect(material.elevation, 1.0);
|
|
expect(padding.padding, const EdgeInsets.all(4.0));
|
|
expect(
|
|
material.shape,
|
|
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12.0))),
|
|
);
|
|
});
|
|
|
|
testWidgets('Card uses values from CardTheme', (WidgetTester tester) async {
|
|
final CardThemeData cardTheme = _cardTheme();
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData(cardTheme: cardTheme),
|
|
home: const Scaffold(body: Card()),
|
|
),
|
|
);
|
|
|
|
final Padding padding = _getCardPadding(tester);
|
|
final Material material = _getCardMaterial(tester);
|
|
|
|
expect(material.clipBehavior, cardTheme.clipBehavior);
|
|
expect(material.color, cardTheme.color);
|
|
expect(material.shadowColor, cardTheme.shadowColor);
|
|
expect(material.surfaceTintColor, cardTheme.surfaceTintColor);
|
|
expect(material.elevation, cardTheme.elevation);
|
|
expect(padding.padding, cardTheme.margin);
|
|
expect(material.shape, cardTheme.shape);
|
|
});
|
|
|
|
testWidgets('Card widget properties take priority over theme', (WidgetTester tester) async {
|
|
const Clip clip = Clip.hardEdge;
|
|
const Color color = Colors.orange;
|
|
const Color shadowColor = Colors.pink;
|
|
const elevation = 7.0;
|
|
const margin = EdgeInsets.all(3.0);
|
|
const ShapeBorder shape = RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.all(Radius.circular(9.0)),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: _themeData().copyWith(cardTheme: _cardTheme()),
|
|
home: const Scaffold(
|
|
body: Card(
|
|
clipBehavior: clip,
|
|
color: color,
|
|
shadowColor: shadowColor,
|
|
elevation: elevation,
|
|
margin: margin,
|
|
shape: shape,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Padding padding = _getCardPadding(tester);
|
|
final Material material = _getCardMaterial(tester);
|
|
|
|
expect(material.clipBehavior, clip);
|
|
expect(material.color, color);
|
|
expect(material.shadowColor, shadowColor);
|
|
expect(material.elevation, elevation);
|
|
expect(padding.padding, margin);
|
|
expect(material.shape, shape);
|
|
});
|
|
|
|
testWidgets('CardTheme properties take priority over ThemeData properties', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final CardThemeData cardTheme = _cardTheme();
|
|
final ThemeData themeData = _themeData().copyWith(cardTheme: cardTheme);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: themeData,
|
|
home: const Scaffold(body: Card()),
|
|
),
|
|
);
|
|
|
|
final Material material = _getCardMaterial(tester);
|
|
expect(material.color, cardTheme.color);
|
|
});
|
|
|
|
testWidgets('Material3 - ThemeData properties are used when no CardTheme is set', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final themeData = ThemeData();
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: themeData,
|
|
home: const Scaffold(body: Card()),
|
|
),
|
|
);
|
|
|
|
final Material material = _getCardMaterial(tester);
|
|
expect(material.color, themeData.colorScheme.surfaceContainerLow);
|
|
});
|
|
|
|
testWidgets('Material3 - CardTheme customizes shape', (WidgetTester tester) async {
|
|
const cardTheme = CardThemeData(
|
|
color: Colors.white,
|
|
shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(7))),
|
|
elevation: 1.0,
|
|
);
|
|
|
|
final Key painterKey = UniqueKey();
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData(cardTheme: cardTheme),
|
|
home: Scaffold(
|
|
body: RepaintBoundary(
|
|
key: painterKey,
|
|
child: Center(
|
|
child: Card(child: SizedBox.fromSize(size: const Size(200, 300))),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await expectLater(find.byKey(painterKey), matchesGoldenFile('card_theme.custom_shape.png'));
|
|
});
|
|
|
|
testWidgets('Card properties are taken over the theme values', (WidgetTester tester) async {
|
|
const Clip themeClipBehavior = Clip.antiAlias;
|
|
const Color themeColor = Colors.red;
|
|
const Color themeShadowColor = Colors.orange;
|
|
const themeElevation = 10.0;
|
|
const themeMargin = EdgeInsets.all(12.0);
|
|
const ShapeBorder themeShape = RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
|
);
|
|
|
|
const Clip clipBehavior = Clip.hardEdge;
|
|
const Color color = Colors.yellow;
|
|
const Color shadowColor = Colors.green;
|
|
const elevation = 20.0;
|
|
const margin = EdgeInsets.all(18.0);
|
|
const ShapeBorder shape = RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.all(Radius.circular(25.0)),
|
|
);
|
|
|
|
final themeData = ThemeData(
|
|
cardTheme: const CardThemeData(
|
|
clipBehavior: themeClipBehavior,
|
|
color: themeColor,
|
|
shadowColor: themeShadowColor,
|
|
elevation: themeElevation,
|
|
margin: themeMargin,
|
|
shape: themeShape,
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: themeData,
|
|
home: const Scaffold(
|
|
body: Card(
|
|
clipBehavior: clipBehavior,
|
|
color: color,
|
|
shadowColor: shadowColor,
|
|
elevation: elevation,
|
|
margin: margin,
|
|
shape: shape,
|
|
child: SizedBox(width: 200, height: 200),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Padding cardMargin = _getCardPadding(tester);
|
|
final Material material = _getCardMaterial(tester);
|
|
|
|
expect(material.clipBehavior, clipBehavior);
|
|
expect(material.color, color);
|
|
expect(material.shadowColor, shadowColor);
|
|
expect(material.elevation, elevation);
|
|
expect(material.shape, shape);
|
|
expect(cardMargin.padding, margin);
|
|
});
|
|
|
|
testWidgets('Local CardTheme can override global CardTheme', (WidgetTester tester) async {
|
|
const Clip globalClipBehavior = Clip.antiAlias;
|
|
const Color globalColor = Colors.red;
|
|
const Color globalShadowColor = Colors.orange;
|
|
const globalElevation = 10.0;
|
|
const globalMargin = EdgeInsets.all(12.0);
|
|
const ShapeBorder globalShape = RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
|
);
|
|
|
|
const Clip localClipBehavior = Clip.hardEdge;
|
|
const Color localColor = Colors.yellow;
|
|
const Color localShadowColor = Colors.green;
|
|
const localElevation = 20.0;
|
|
const localMargin = EdgeInsets.all(18.0);
|
|
const ShapeBorder localShape = RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.all(Radius.circular(25.0)),
|
|
);
|
|
|
|
final themeData = ThemeData(
|
|
cardTheme: const CardThemeData(
|
|
clipBehavior: globalClipBehavior,
|
|
color: globalColor,
|
|
shadowColor: globalShadowColor,
|
|
elevation: globalElevation,
|
|
margin: globalMargin,
|
|
shape: globalShape,
|
|
),
|
|
);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: themeData,
|
|
home: const Scaffold(
|
|
body: CardTheme(
|
|
data: CardThemeData(
|
|
clipBehavior: localClipBehavior,
|
|
color: localColor,
|
|
shadowColor: localShadowColor,
|
|
elevation: localElevation,
|
|
margin: localMargin,
|
|
shape: localShape,
|
|
),
|
|
child: Card(child: SizedBox(width: 200, height: 200)),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Padding cardMargin = _getCardPadding(tester);
|
|
final Material material = _getCardMaterial(tester);
|
|
|
|
expect(material.clipBehavior, localClipBehavior);
|
|
expect(material.color, localColor);
|
|
expect(material.shadowColor, localShadowColor);
|
|
expect(material.elevation, localElevation);
|
|
expect(material.shape, localShape);
|
|
expect(cardMargin.padding, localMargin);
|
|
});
|
|
|
|
group('Material 2', () {
|
|
// These tests are only relevant for Material 2. Once Material 2
|
|
// support is deprecated and the APIs are removed, these tests
|
|
// can be deleted.
|
|
|
|
testWidgets('Material2 - ThemeData properties are used when no CardTheme is set', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final themeData = ThemeData(useMaterial3: false);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: themeData,
|
|
home: const Scaffold(body: Card()),
|
|
),
|
|
);
|
|
|
|
final Material material = _getCardMaterial(tester);
|
|
expect(material.color, themeData.cardColor);
|
|
});
|
|
|
|
testWidgets('Material2 - Passing no CardTheme returns defaults', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: const Scaffold(body: Card()),
|
|
),
|
|
);
|
|
|
|
final Padding padding = _getCardPadding(tester);
|
|
final Material material = _getCardMaterial(tester);
|
|
|
|
expect(material.clipBehavior, Clip.none);
|
|
expect(material.color, Colors.white);
|
|
expect(material.shadowColor, Colors.black);
|
|
expect(material.surfaceTintColor, null);
|
|
expect(material.elevation, 1.0);
|
|
expect(padding.padding, const EdgeInsets.all(4.0));
|
|
expect(
|
|
material.shape,
|
|
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))),
|
|
);
|
|
});
|
|
|
|
testWidgets('Material2 - CardTheme customizes shape', (WidgetTester tester) async {
|
|
const cardTheme = CardThemeData(
|
|
color: Colors.white,
|
|
shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(7))),
|
|
elevation: 1.0,
|
|
);
|
|
|
|
final Key painterKey = UniqueKey();
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData(cardTheme: cardTheme, useMaterial3: false),
|
|
home: Scaffold(
|
|
body: RepaintBoundary(
|
|
key: painterKey,
|
|
child: Center(
|
|
child: Card(child: SizedBox.fromSize(size: const Size(200, 300))),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await expectLater(
|
|
find.byKey(painterKey),
|
|
matchesGoldenFile('card_theme.custom_shape_m2.png'),
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
CardThemeData _cardTheme() {
|
|
return const CardThemeData(
|
|
clipBehavior: Clip.antiAlias,
|
|
color: Colors.green,
|
|
shadowColor: Colors.red,
|
|
surfaceTintColor: Colors.purple,
|
|
elevation: 6.0,
|
|
margin: EdgeInsets.all(7.0),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0))),
|
|
);
|
|
}
|
|
|
|
ThemeData _themeData() {
|
|
return ThemeData(cardColor: Colors.pink);
|
|
}
|
|
|
|
Material _getCardMaterial(WidgetTester tester) {
|
|
return tester.widget<Material>(
|
|
find.descendant(of: find.byType(Card), matching: find.byType(Material)),
|
|
);
|
|
}
|
|
|
|
Padding _getCardPadding(WidgetTester tester) {
|
|
return tester.widget<Padding>(
|
|
find.descendant(of: find.byType(Card), matching: find.byType(Padding)),
|
|
);
|
|
}
|