Fix out of sync templates files and add a check (#145747)
### Description - Add a check to verify template code in the Material library is synced with `gen_defaults` - Sync the changes to pass the new check.
This commit is contained in:
parent
ac5be2d821
commit
9436b3c1c8
@ -12,6 +12,7 @@ import 'package:analyzer/dart/analysis/results.dart';
|
||||
import 'package:analyzer/dart/analysis/utilities.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
import 'package:collection/equality.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
@ -252,6 +253,10 @@ Future<void> run(List<String> arguments) async {
|
||||
printProgress('Correct file names in gen_defaults.dart...');
|
||||
await verifyTokenTemplatesUpdateCorrectFiles(flutterRoot);
|
||||
|
||||
// Ensure material library files are up-to-date with the token template files.
|
||||
printProgress('Material library files are up-to-date with token template files...');
|
||||
await verifyMaterialFilesAreUpToDateWithTemplateFiles(flutterRoot, dart);
|
||||
|
||||
// Ensure integration test files are up-to-date with the app template.
|
||||
printProgress('Up to date integration test template files...');
|
||||
await verifyIntegrationTestTemplateFiles(flutterRoot);
|
||||
@ -399,6 +404,78 @@ Future<void> verifyTokenTemplatesUpdateCorrectFiles(String workingDirectory) asy
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify Material library files are up-to-date with the token template files
|
||||
/// when running /dev/tools/gen_defaults/bin/gen_defaults.dart.
|
||||
Future<void> verifyMaterialFilesAreUpToDateWithTemplateFiles(String workingDirectory, String dartExecutable) async {
|
||||
final List<String> errors = <String>[];
|
||||
const String beginGeneratedComment = '// BEGIN GENERATED TOKEN PROPERTIES';
|
||||
|
||||
String getMaterialDirPath(List<String> lines) {
|
||||
final String line = lines.firstWhere((String line) => line.contains('String materialLib'));
|
||||
final String relativePath = line.substring(line.indexOf("'") + 1, line.lastIndexOf("'"));
|
||||
return path.join(workingDirectory, relativePath);
|
||||
}
|
||||
|
||||
String getFileName(String line) {
|
||||
const String materialLibString = r"'$materialLib/";
|
||||
final String leftClamp = line.substring(line.indexOf(materialLibString) + materialLibString.length);
|
||||
return leftClamp.substring(0, leftClamp.indexOf("'"));
|
||||
}
|
||||
|
||||
// Get the template generated code from the file.
|
||||
List<String> getGeneratedCode(List<String> lines) {
|
||||
return lines.skipWhile((String line) => !line.contains(beginGeneratedComment)).toList();
|
||||
}
|
||||
|
||||
final String genDefaultsBinDir = '$workingDirectory/dev/tools/gen_defaults/bin';
|
||||
final File file = File(path.join(genDefaultsBinDir, 'gen_defaults.dart'));
|
||||
final List<String> lines = file.readAsLinesSync();
|
||||
final String materialDirPath = getMaterialDirPath(lines);
|
||||
final Map<String, List<String>> beforeGeneratedCode = <String, List<String>>{};
|
||||
final Map<String, List<String>> afterGeneratedCode = <String, List<String>>{};
|
||||
|
||||
for (final String line in lines) {
|
||||
if (line.contains('updateFile();')) {
|
||||
final String fileName = getFileName(line);
|
||||
final String filePath = path.join(materialDirPath, fileName);
|
||||
final File file = File(filePath);
|
||||
beforeGeneratedCode[fileName] = getGeneratedCode(file.readAsLinesSync());
|
||||
}
|
||||
}
|
||||
|
||||
// Run gen_defaults.dart to generate the token template files.
|
||||
await runCommand(dartExecutable,
|
||||
<String>['--enable-asserts', path.join('dev', 'tools', 'gen_defaults', 'bin', 'gen_defaults.dart')],
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
|
||||
for (final String line in lines) {
|
||||
if (line.contains('updateFile();')) {
|
||||
final String fileName = getFileName(line);
|
||||
final String filePath = path.join(materialDirPath, fileName);
|
||||
final File file = File(filePath);
|
||||
afterGeneratedCode[fileName] = getGeneratedCode(file.readAsLinesSync());
|
||||
}
|
||||
}
|
||||
|
||||
// Compare the generated code before and after running gen_defaults.dart.
|
||||
for (final String fileName in beforeGeneratedCode.keys) {
|
||||
final List<String> before = beforeGeneratedCode[fileName]!;
|
||||
final List<String> after = afterGeneratedCode[fileName]!;
|
||||
if (!const IterableEquality<String>().equals(before, after)) {
|
||||
errors.add('$fileName is not up-to-date with the token template file.');
|
||||
}
|
||||
}
|
||||
|
||||
// Fail if any errors.
|
||||
if (errors.isNotEmpty) {
|
||||
foundError(<String>[
|
||||
...errors,
|
||||
'${bold}See: https://github.com/flutter/flutter/blob/master/dev/tools/gen_defaults to update the token template files.$reset',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify tool test files end in `_test.dart`.
|
||||
///
|
||||
/// The test runner will only recognize files ending in `_test.dart` as tests to
|
||||
|
@ -0,0 +1,16 @@
|
||||
// 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.
|
||||
|
||||
import 'template.dart';
|
||||
|
||||
class ChipTemplate extends TokenTemplate {
|
||||
const ChipTemplate(super.blockName, super.fileName, super.tokens);
|
||||
|
||||
@override
|
||||
String generate() => '''
|
||||
class _${blockName}DefaultsM3 {
|
||||
final String testString = 'This is chip_template.dart class.';
|
||||
}
|
||||
''';
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
// 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.
|
||||
|
||||
import 'chip_template.dart';
|
||||
|
||||
const String materialLib = 'packages/flutter/lib/src/material';
|
||||
|
||||
Future<void> main(List<String> args) async {
|
||||
final Map<String, dynamic> tokens = <String, dynamic>{};
|
||||
ChipTemplate('Chip', '$materialLib/chip.dart', tokens).updateFile();
|
||||
}
|
@ -0,0 +1,279 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'token_logger.dart';
|
||||
|
||||
/// Base class for code generation templates.
|
||||
abstract class TokenTemplate {
|
||||
const TokenTemplate(this.blockName, this.fileName, this._tokens, {
|
||||
this.colorSchemePrefix = 'Theme.of(context).colorScheme.',
|
||||
this.textThemePrefix = 'Theme.of(context).textTheme.'
|
||||
});
|
||||
|
||||
/// Name of the code block that this template will generate.
|
||||
///
|
||||
/// Used to identify an existing block when updating it.
|
||||
final String blockName;
|
||||
|
||||
/// Name of the file that will be updated with the generated code.
|
||||
final String fileName;
|
||||
|
||||
/// Map of token data extracted from the Material Design token database.
|
||||
final Map<String, dynamic> _tokens;
|
||||
|
||||
/// Optional prefix prepended to color definitions.
|
||||
///
|
||||
/// Defaults to 'Theme.of(context).colorScheme.'
|
||||
final String colorSchemePrefix;
|
||||
|
||||
/// Optional prefix prepended to text style definitions.
|
||||
///
|
||||
/// Defaults to 'Theme.of(context).textTheme.'
|
||||
final String textThemePrefix;
|
||||
|
||||
/// Check if a token is available.
|
||||
bool tokenAvailable(String tokenName) => _tokens.containsKey(tokenName);
|
||||
|
||||
/// Resolve a token while logging its usage.
|
||||
/// There will be no log if [optional] is true and the token doesn't exist.
|
||||
dynamic getToken(String tokenName, {bool optional = false}) {
|
||||
if (optional && !tokenAvailable(tokenName)) {
|
||||
return null;
|
||||
}
|
||||
tokenLogger.log(tokenName);
|
||||
return _tokens[tokenName];
|
||||
}
|
||||
|
||||
static const String beginGeneratedComment = '''
|
||||
|
||||
// BEGIN GENERATED TOKEN PROPERTIES''';
|
||||
|
||||
static const String headerComment = '''
|
||||
|
||||
// Do not edit by hand. The code between the "BEGIN GENERATED" and
|
||||
// "END GENERATED" comments are generated from data in the Material
|
||||
// Design token database by the script:
|
||||
// dev/tools/gen_defaults/bin/gen_defaults.dart.
|
||||
|
||||
''';
|
||||
|
||||
static const String endGeneratedComment = '''
|
||||
|
||||
// END GENERATED TOKEN PROPERTIES''';
|
||||
|
||||
/// Replace or append the contents of the file with the text from [generate].
|
||||
///
|
||||
/// If the file already contains a generated text block matching the
|
||||
/// [blockName], it will be replaced by the [generate] output. Otherwise
|
||||
/// the content will just be appended to the end of the file.
|
||||
Future<void> updateFile() async {
|
||||
final String contents = File(fileName).readAsStringSync();
|
||||
final String beginComment = '$beginGeneratedComment - $blockName\n';
|
||||
final String endComment = '$endGeneratedComment - $blockName\n';
|
||||
final int beginPreviousBlock = contents.indexOf(beginComment);
|
||||
final int endPreviousBlock = contents.indexOf(endComment);
|
||||
late String contentBeforeBlock;
|
||||
late String contentAfterBlock;
|
||||
if (beginPreviousBlock != -1) {
|
||||
if (endPreviousBlock < beginPreviousBlock) {
|
||||
print('Unable to find block named $blockName in $fileName, skipping code generation.');
|
||||
return;
|
||||
}
|
||||
// Found a valid block matching the name, so record the content before and after.
|
||||
contentBeforeBlock = contents.substring(0, beginPreviousBlock);
|
||||
contentAfterBlock = contents.substring(endPreviousBlock + endComment.length);
|
||||
} else {
|
||||
// Just append to the bottom.
|
||||
contentBeforeBlock = contents;
|
||||
contentAfterBlock = '';
|
||||
}
|
||||
|
||||
final StringBuffer buffer = StringBuffer(contentBeforeBlock);
|
||||
buffer.write(beginComment);
|
||||
buffer.write(headerComment);
|
||||
buffer.write(generate());
|
||||
buffer.write(endComment);
|
||||
buffer.write(contentAfterBlock);
|
||||
File(fileName).writeAsStringSync(buffer.toString());
|
||||
}
|
||||
|
||||
/// Provide the generated content for the template.
|
||||
///
|
||||
/// This abstract method needs to be implemented by subclasses
|
||||
/// to provide the content that [updateFile] will append to the
|
||||
/// bottom of the file.
|
||||
String generate();
|
||||
|
||||
/// Generate a [ColorScheme] color name for the given token.
|
||||
///
|
||||
/// If there is a value for the given token, this will return
|
||||
/// the value prepended with [colorSchemePrefix].
|
||||
///
|
||||
/// Otherwise it will return [defaultValue] if provided or 'null' if not.
|
||||
///
|
||||
/// If a [defaultValue] is not provided and the token doesn't exist, the token
|
||||
/// lookup is logged and a warning will be shown at the end of the process.
|
||||
///
|
||||
/// See also:
|
||||
/// * [componentColor], that provides support for an optional opacity.
|
||||
String color(String colorToken, [String? defaultValue]) {
|
||||
final String effectiveDefault = defaultValue ?? 'null';
|
||||
final dynamic tokenVal = getToken(colorToken, optional: defaultValue != null);
|
||||
return tokenVal == null ? effectiveDefault : '$colorSchemePrefix$tokenVal';
|
||||
}
|
||||
|
||||
/// Generate a [ColorScheme] color name for the given token or a transparent
|
||||
/// color if there is no value for the token.
|
||||
///
|
||||
/// If there is a value for the given token, this will return
|
||||
/// the value prepended with [colorSchemePrefix].
|
||||
///
|
||||
/// Otherwise it will return 'Colors.transparent'.
|
||||
///
|
||||
/// See also:
|
||||
/// * [componentColor], that provides support for an optional opacity.
|
||||
String? colorOrTransparent(String token) => color(token, 'Colors.transparent');
|
||||
|
||||
/// Generate a [ColorScheme] color name for the given component's color
|
||||
/// with opacity if available.
|
||||
///
|
||||
/// If there is a value for the given component's color, this will return
|
||||
/// the value prepended with [colorSchemePrefix]. If there is also
|
||||
/// an opacity specified for the component, then the returned value
|
||||
/// will include this opacity calculation.
|
||||
///
|
||||
/// If there is no value for the component's color, 'null' will be returned.
|
||||
///
|
||||
/// See also:
|
||||
/// * [color], that provides support for looking up a raw color token.
|
||||
String componentColor(String componentToken) {
|
||||
final String colorToken = '$componentToken.color';
|
||||
if (!tokenAvailable(colorToken)) {
|
||||
return 'null';
|
||||
}
|
||||
String value = color(colorToken);
|
||||
final String opacityToken = '$componentToken.opacity';
|
||||
if (tokenAvailable(opacityToken)) {
|
||||
value += '.withOpacity(${opacity(opacityToken)})';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// Generate the opacity value for the given token.
|
||||
String? opacity(String token) {
|
||||
tokenLogger.log(token);
|
||||
return _numToString(getToken(token));
|
||||
}
|
||||
|
||||
String? _numToString(Object? value, [int? digits]) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value is num) {
|
||||
if (value == double.infinity) {
|
||||
return 'double.infinity';
|
||||
}
|
||||
return digits == null ? value.toString() : value.toStringAsFixed(digits);
|
||||
}
|
||||
return getToken(value as String).toString();
|
||||
}
|
||||
|
||||
/// Generate an elevation value for the given component token.
|
||||
String elevation(String componentToken) {
|
||||
return getToken(getToken('$componentToken.elevation')! as String)!.toString();
|
||||
}
|
||||
|
||||
/// Generate a size value for the given component token.
|
||||
///
|
||||
/// Non-square sizes are specified as width and height.
|
||||
String size(String componentToken) {
|
||||
final String sizeToken = '$componentToken.size';
|
||||
if (!tokenAvailable(sizeToken)) {
|
||||
final String widthToken = '$componentToken.width';
|
||||
final String heightToken = '$componentToken.height';
|
||||
if (!tokenAvailable(widthToken) && !tokenAvailable(heightToken)) {
|
||||
throw Exception('Unable to find width, height, or size tokens for $componentToken');
|
||||
}
|
||||
final String? width = _numToString(tokenAvailable(widthToken) ? getToken(widthToken)! as num : double.infinity, 0);
|
||||
final String? height = _numToString(tokenAvailable(heightToken) ? getToken(heightToken)! as num : double.infinity, 0);
|
||||
return 'const Size($width, $height)';
|
||||
}
|
||||
return 'const Size.square(${_numToString(getToken(sizeToken))})';
|
||||
}
|
||||
|
||||
/// Generate a shape constant for the given component token.
|
||||
///
|
||||
/// Currently supports family:
|
||||
/// - "SHAPE_FAMILY_ROUNDED_CORNERS" which maps to [RoundedRectangleBorder].
|
||||
/// - "SHAPE_FAMILY_CIRCULAR" which maps to a [StadiumBorder].
|
||||
String shape(String componentToken, [String prefix = 'const ']) {
|
||||
|
||||
final Map<String, dynamic> shape = getToken(getToken('$componentToken.shape') as String) as Map<String, dynamic>;
|
||||
switch (shape['family']) {
|
||||
case 'SHAPE_FAMILY_ROUNDED_CORNERS':
|
||||
final double topLeft = shape['topLeft'] as double;
|
||||
final double topRight = shape['topRight'] as double;
|
||||
final double bottomLeft = shape['bottomLeft'] as double;
|
||||
final double bottomRight = shape['bottomRight'] as double;
|
||||
if (topLeft == topRight && topLeft == bottomLeft && topLeft == bottomRight) {
|
||||
if (topLeft == 0) {
|
||||
return '${prefix}RoundedRectangleBorder()';
|
||||
}
|
||||
return '${prefix}RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular($topLeft)))';
|
||||
}
|
||||
if (topLeft == topRight && bottomLeft == bottomRight) {
|
||||
return '${prefix}RoundedRectangleBorder(borderRadius: BorderRadius.vertical('
|
||||
'${topLeft > 0 ? 'top: Radius.circular($topLeft)':''}'
|
||||
'${topLeft > 0 && bottomLeft > 0 ? ',':''}'
|
||||
'${bottomLeft > 0 ? 'bottom: Radius.circular($bottomLeft)':''}'
|
||||
'))';
|
||||
}
|
||||
return '${prefix}RoundedRectangleBorder(borderRadius: '
|
||||
'BorderRadius.only('
|
||||
'topLeft: Radius.circular(${shape['topLeft']}), '
|
||||
'topRight: Radius.circular(${shape['topRight']}), '
|
||||
'bottomLeft: Radius.circular(${shape['bottomLeft']}), '
|
||||
'bottomRight: Radius.circular(${shape['bottomRight']})))';
|
||||
case 'SHAPE_FAMILY_CIRCULAR':
|
||||
return '${prefix}StadiumBorder()';
|
||||
}
|
||||
print('Unsupported shape family type: ${shape['family']} for $componentToken');
|
||||
return '';
|
||||
}
|
||||
|
||||
/// Generate a [BorderSide] for the given component.
|
||||
String border(String componentToken) {
|
||||
|
||||
if (!tokenAvailable('$componentToken.color')) {
|
||||
return 'null';
|
||||
}
|
||||
final String borderColor = componentColor(componentToken);
|
||||
final double width = (
|
||||
getToken('$componentToken.width', optional: true) ??
|
||||
getToken('$componentToken.height', optional: true) ??
|
||||
1.0
|
||||
) as double;
|
||||
return 'BorderSide(color: $borderColor${width != 1.0 ? ", width: $width" : ""})';
|
||||
}
|
||||
|
||||
/// Generate a [TextTheme] text style name for the given component token.
|
||||
String textStyle(String componentToken) {
|
||||
|
||||
return '$textThemePrefix${getToken("$componentToken.text-style")}';
|
||||
}
|
||||
|
||||
String textStyleWithColor(String componentToken) {
|
||||
|
||||
if (!tokenAvailable('$componentToken.text-style')) {
|
||||
return 'null';
|
||||
}
|
||||
String style = textStyle(componentToken);
|
||||
if (tokenAvailable('$componentToken.color')) {
|
||||
style = '$style?.copyWith(color: ${componentColor(componentToken)})';
|
||||
}
|
||||
return style;
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:collection';
|
||||
import 'dart:io';
|
||||
|
||||
final TokenLogger tokenLogger = TokenLogger();
|
||||
|
||||
/// Class to keep track of used tokens and versions.
|
||||
class TokenLogger {
|
||||
TokenLogger();
|
||||
|
||||
void init({
|
||||
required Map<String, dynamic> allTokens,
|
||||
required Map<String, List<String>> versionMap
|
||||
}){
|
||||
_allTokens = allTokens;
|
||||
_versionMap = versionMap;
|
||||
}
|
||||
|
||||
/// Map of all tokens to their values.
|
||||
late Map<String, dynamic> _allTokens;
|
||||
|
||||
// Map of versions to their token files.
|
||||
late Map<String, List<String>> _versionMap;
|
||||
|
||||
// Sorted set of used tokens.
|
||||
final SplayTreeSet<String> _usedTokens = SplayTreeSet<String>();
|
||||
|
||||
// Set of tokens that were referenced on some templates, but do not exist.
|
||||
final Set<String> _unavailableTokens = <String>{};
|
||||
|
||||
void clear() {
|
||||
_allTokens.clear();
|
||||
_versionMap.clear();
|
||||
_usedTokens.clear();
|
||||
_unavailableTokens.clear();
|
||||
}
|
||||
|
||||
/// Logs a token.
|
||||
void log(String token) {
|
||||
if (!_allTokens.containsKey(token)) {
|
||||
_unavailableTokens.add(token);
|
||||
return;
|
||||
}
|
||||
_usedTokens.add(token);
|
||||
}
|
||||
|
||||
/// Prints version usage to the console.
|
||||
void printVersionUsage({required bool verbose}) {
|
||||
final String versionsString = 'Versions used: ${_versionMap.keys.join(', ')}';
|
||||
print(versionsString);
|
||||
if (verbose) {
|
||||
for (final String version in _versionMap.keys) {
|
||||
print(' $version:');
|
||||
final List<String> files = List<String>.from(_versionMap[version]!);
|
||||
files.sort();
|
||||
for (final String file in files) {
|
||||
print(' $file');
|
||||
}
|
||||
}
|
||||
print('');
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints tokens usage to the console.
|
||||
void printTokensUsage({required bool verbose}) {
|
||||
final Set<String> allTokensSet = _allTokens.keys.toSet();
|
||||
|
||||
if (verbose) {
|
||||
for (final String token in SplayTreeSet<String>.from(allTokensSet).toList()) {
|
||||
if (_usedTokens.contains(token)) {
|
||||
print('✅ $token');
|
||||
} else {
|
||||
print('❌ $token');
|
||||
}
|
||||
}
|
||||
print('');
|
||||
}
|
||||
|
||||
print('Tokens used: ${_usedTokens.length}/${_allTokens.length}');
|
||||
|
||||
if (_unavailableTokens.isNotEmpty) {
|
||||
print('');
|
||||
print('\x1B[31m' 'Some referenced tokens do not exist: ${_unavailableTokens.length}' '\x1B[0m');
|
||||
for (final String token in _unavailableTokens) {
|
||||
print(' $token');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dumps version and tokens usage to a file.
|
||||
void dumpToFile(String path) {
|
||||
final File file = File(path);
|
||||
file.createSync(recursive: true);
|
||||
final String versionsString = 'Versions used, ${_versionMap.keys.join(', ')}';
|
||||
file.writeAsStringSync('$versionsString\n${_usedTokens.join(',\n')}\n');
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
// 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.
|
||||
|
||||
// ignore: unused_element
|
||||
final _ChipDefaultsM3 _chipDefaultsM3 = _ChipDefaultsM3();
|
||||
|
||||
// BEGIN GENERATED TOKEN PROPERTIES - Chip
|
||||
|
||||
// Do not edit by hand. The code between the "BEGIN GENERATED" and
|
||||
// "END GENERATED" comments are generated from data in the Material
|
||||
// Design token database by the script:
|
||||
// dev/tools/gen_defaults/bin/gen_defaults.dart.
|
||||
|
||||
class _ChipDefaultsM3 {
|
||||
final String testString = 'This is chip.dart class.';
|
||||
}
|
||||
|
||||
// END GENERATED TOKEN PROPERTIES - Chip
|
@ -45,6 +45,7 @@ void main() {
|
||||
final String testRootPath = path.join('test', 'analyze-test-input', 'root');
|
||||
final String dartName = Platform.isWindows ? 'dart.exe' : 'dart';
|
||||
final String dartPath = path.canonicalize(path.join('..', '..', 'bin', 'cache', 'dart-sdk', 'bin', dartName));
|
||||
final String testGenDefaultsPath = path.join('test', 'analyze-gen-defaults');
|
||||
|
||||
test('analyze.dart - verifyDeprecations', () async {
|
||||
final String result = await capture(() => verifyDeprecations(testRootPath, minimumMatches: 2), shouldHaveErrors: true);
|
||||
@ -293,4 +294,24 @@ void main() {
|
||||
'╚═══════════════════════════════════════════════════════════════════════════════\n'
|
||||
);
|
||||
});
|
||||
|
||||
test('analyze.dart - verifyMaterialFilesAreUpToDateWithTemplateFiles', () async {
|
||||
String result = await capture(() => verifyMaterialFilesAreUpToDateWithTemplateFiles(
|
||||
testGenDefaultsPath,
|
||||
dartPath,
|
||||
), shouldHaveErrors: true);
|
||||
final String lines = <String>[
|
||||
'║ chip.dart is not up-to-date with the token template file.',
|
||||
]
|
||||
.map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/'))
|
||||
.join('\n');
|
||||
const String errorStart = '╔═';
|
||||
result = result.substring(result.indexOf(errorStart));
|
||||
expect(result,
|
||||
'╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n'
|
||||
'$lines\n'
|
||||
'║ See: https://github.com/flutter/flutter/blob/master/dev/tools/gen_defaults to update the token template files.\n'
|
||||
'╚═══════════════════════════════════════════════════════════════════════════════\n'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -43,20 +43,20 @@ class _${blockName}DefaultsM3 extends InputDecorationTheme {
|
||||
return ${border('md.comp.filled-text-field.disabled.active-indicator')};
|
||||
}
|
||||
if (states.contains(MaterialState.error)) {
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return ${border('md.comp.filled-text-field.error.hover.active-indicator')};
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return ${mergedBorder('md.comp.filled-text-field.error.focus.active-indicator','md.comp.filled-text-field.focus.active-indicator')};
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return ${border('md.comp.filled-text-field.error.hover.active-indicator')};
|
||||
}
|
||||
return ${border('md.comp.filled-text-field.error.active-indicator')};
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return ${border('md.comp.filled-text-field.hover.active-indicator')};
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return ${border('md.comp.filled-text-field.focus.active-indicator')};
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return ${border('md.comp.filled-text-field.hover.active-indicator')};
|
||||
}
|
||||
return ${border('md.comp.filled-text-field.active-indicator')};
|
||||
});
|
||||
|
||||
@ -66,20 +66,20 @@ class _${blockName}DefaultsM3 extends InputDecorationTheme {
|
||||
return ${border('md.comp.outlined-text-field.disabled.outline')};
|
||||
}
|
||||
if (states.contains(MaterialState.error)) {
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return ${border('md.comp.outlined-text-field.error.hover.outline')};
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return ${mergedBorder('md.comp.outlined-text-field.error.focus.outline','md.comp.outlined-text-field.focus.outline')};
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return ${border('md.comp.outlined-text-field.error.hover.outline')};
|
||||
}
|
||||
return ${border('md.comp.outlined-text-field.error.outline')};
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return ${border('md.comp.outlined-text-field.hover.outline')};
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return ${border('md.comp.outlined-text-field.focus.outline')};
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return ${border('md.comp.outlined-text-field.hover.outline')};
|
||||
}
|
||||
return ${border('md.comp.outlined-text-field.outline')};
|
||||
});
|
||||
|
||||
|
@ -1535,6 +1535,7 @@ class _PopupMenuDefaultsM3 extends PopupMenuThemeData {
|
||||
|
||||
@override MaterialStateProperty<TextStyle?>? get labelTextStyle {
|
||||
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
// TODO(quncheng): Update this hard-coded value to use the latest tokens.
|
||||
final TextStyle style = _textTheme.labelLarge!;
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return style.apply(color: _colors.onSurface.withOpacity(0.38));
|
||||
|
@ -2441,7 +2441,7 @@ class _TabsPrimaryDefaultsM3 extends TabBarTheme {
|
||||
static double indicatorWeight(TabBarIndicatorSize indicatorSize) {
|
||||
return switch (indicatorSize) {
|
||||
TabBarIndicatorSize.label => 3.0,
|
||||
TabBarIndicatorSize.tab => 2.0,
|
||||
TabBarIndicatorSize.tab => 2.0,
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user