mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add macos project auto migration code for FlutterApplication (#122336)
Add macos project auto migration code for FlutterApplication
This commit is contained in:
parent
2ba0d0d73f
commit
c2f5bf99f1
@ -27,6 +27,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>FlutterApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -27,6 +27,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>FlutterApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -27,6 +27,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>FlutterApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -29,6 +29,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>FlutterApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -27,6 +27,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>FlutterApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -27,6 +27,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>FlutterApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -27,6 +27,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>FlutterApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -27,6 +27,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>FlutterApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -27,6 +27,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>FlutterApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -27,6 +27,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>FlutterApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -27,6 +27,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>FlutterApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -27,6 +27,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>FlutterApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -27,6 +27,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>FlutterApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -30,6 +30,9 @@ class PlistParser {
|
||||
static const String kCFBundleVersionKey = 'CFBundleVersion';
|
||||
static const String kCFBundleDisplayNameKey = 'CFBundleDisplayName';
|
||||
static const String kMinimumOSVersionKey = 'MinimumOSVersion';
|
||||
static const String kNSPrincipalClassKey = 'NSPrincipalClass';
|
||||
|
||||
static const String _plutilExecutable = '/usr/bin/plutil';
|
||||
|
||||
/// Returns the content, converted to XML, of the plist file located at
|
||||
/// [plistFilePath].
|
||||
@ -39,12 +42,11 @@ class PlistParser {
|
||||
///
|
||||
/// The [plistFilePath] argument must not be null.
|
||||
String? plistXmlContent(String plistFilePath) {
|
||||
const String executable = '/usr/bin/plutil';
|
||||
if (!_fileSystem.isFileSync(executable)) {
|
||||
throw const FileNotFoundException(executable);
|
||||
if (!_fileSystem.isFileSync(_plutilExecutable)) {
|
||||
throw const FileNotFoundException(_plutilExecutable);
|
||||
}
|
||||
final List<String> args = <String>[
|
||||
executable, '-convert', 'xml1', '-o', '-', plistFilePath,
|
||||
_plutilExecutable, '-convert', 'xml1', '-o', '-', plistFilePath,
|
||||
];
|
||||
try {
|
||||
final String xmlContent = _processUtils.runSync(
|
||||
@ -53,11 +55,42 @@ class PlistParser {
|
||||
).stdout.trim();
|
||||
return xmlContent;
|
||||
} on ProcessException catch (error) {
|
||||
_logger.printTrace('$error');
|
||||
_logger.printError('$error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces the string key in the given plist file with the given value.
|
||||
///
|
||||
/// If the value is null, then the key will be removed.
|
||||
///
|
||||
/// Returns true if successful.
|
||||
bool replaceKey(String plistFilePath, {required String key, String? value }) {
|
||||
if (!_fileSystem.isFileSync(_plutilExecutable)) {
|
||||
throw const FileNotFoundException(_plutilExecutable);
|
||||
}
|
||||
final List<String> args;
|
||||
if (value == null) {
|
||||
args = <String>[
|
||||
_plutilExecutable, '-remove', key, plistFilePath,
|
||||
];
|
||||
} else {
|
||||
args = <String>[
|
||||
_plutilExecutable, '-replace', key, '-string', value, plistFilePath,
|
||||
];
|
||||
}
|
||||
try {
|
||||
_processUtils.runSync(
|
||||
args,
|
||||
throwOnError: true,
|
||||
);
|
||||
} on ProcessException catch (error) {
|
||||
_logger.printError('$error');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Parses the plist file located at [plistFilePath] and returns the
|
||||
/// associated map of key/value property list pairs.
|
||||
///
|
||||
|
||||
@ -17,6 +17,7 @@ import '../migrations/xcode_script_build_phase_migration.dart';
|
||||
import '../migrations/xcode_thin_binary_build_phase_input_paths_migration.dart';
|
||||
import '../project.dart';
|
||||
import 'cocoapod_utils.dart';
|
||||
import 'migrations/flutter_application_migration.dart';
|
||||
import 'migrations/macos_deployment_target_migration.dart';
|
||||
import 'migrations/remove_macos_framework_link_and_embedding_migration.dart';
|
||||
|
||||
@ -57,6 +58,7 @@ Future<void> buildMacOS({
|
||||
XcodeProjectObjectVersionMigration(flutterProject.macos, globals.logger),
|
||||
XcodeScriptBuildPhaseMigration(flutterProject.macos, globals.logger),
|
||||
XcodeThinBinaryBuildPhaseInputPathsMigration(flutterProject.macos, globals.logger),
|
||||
FlutterApplicationMigration(flutterProject.macos, globals.logger),
|
||||
];
|
||||
|
||||
final ProjectMigration migration = ProjectMigration(migrators);
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
// 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 '../../base/file_system.dart';
|
||||
import '../../base/project_migrator.dart';
|
||||
import '../../globals.dart' as globals;
|
||||
import '../../ios/plist_parser.dart';
|
||||
import '../../xcode_project.dart';
|
||||
|
||||
/// Update the minimum macOS deployment version to the minimum allowed by Xcode without causing a warning.
|
||||
class FlutterApplicationMigration extends ProjectMigrator {
|
||||
FlutterApplicationMigration(
|
||||
MacOSProject project,
|
||||
super.logger,
|
||||
) : _infoPlistFile = project.defaultHostInfoPlist;
|
||||
|
||||
final File _infoPlistFile;
|
||||
|
||||
@override
|
||||
void migrate() {
|
||||
if (_infoPlistFile.existsSync()) {
|
||||
final String? principleClass =
|
||||
globals.plistParser.getStringValueFromFile(_infoPlistFile.path, PlistParser.kNSPrincipalClassKey);
|
||||
if (principleClass == null || principleClass == 'FlutterApplication') {
|
||||
// No NSPrincipalClass defined, or already converted, so no migration
|
||||
// needed.
|
||||
return;
|
||||
}
|
||||
if (principleClass != 'NSApplication') {
|
||||
// Only replace NSApplication values, since we don't know why they might
|
||||
// have substituted something else.
|
||||
logger.printTrace('${_infoPlistFile.basename} has an '
|
||||
'${PlistParser.kNSPrincipalClassKey} of $principleClass, not '
|
||||
'NSApplication, skipping FlutterApplication migration.\nYou will need '
|
||||
'to modify your application class to derive from FlutterApplication.');
|
||||
return;
|
||||
}
|
||||
logger.printStatus('Updating ${_infoPlistFile.basename} to use FlutterApplication instead of NSApplication.');
|
||||
final bool success = globals.plistParser.replaceKey(_infoPlistFile.path, key: PlistParser.kNSPrincipalClassKey, value: 'FlutterApplication');
|
||||
if (!success) {
|
||||
logger.printError('Updating ${_infoPlistFile.basename} failed.');
|
||||
}
|
||||
} else {
|
||||
logger.printTrace('${_infoPlistFile.basename} not found, skipping FlutterApplication migration.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -27,6 +27,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>FlutterApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -5,13 +5,17 @@
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/ios/plist_parser.dart';
|
||||
import 'package:flutter_tools/src/macos/migrations/flutter_application_migration.dart';
|
||||
import 'package:flutter_tools/src/macos/migrations/macos_deployment_target_migration.dart';
|
||||
import 'package:flutter_tools/src/macos/migrations/remove_macos_framework_link_and_embedding_migration.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||
import 'package:flutter_tools/src/xcode_project.dart';
|
||||
import 'package:test/fake.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/fakes.dart';
|
||||
|
||||
void main() {
|
||||
group('remove link and embed migration', () {
|
||||
@ -275,12 +279,107 @@ platform :osx, '10.14'
|
||||
expect('Updating minimum macOS deployment target to 10.14'.allMatches(testLogger.statusText).length, 1);
|
||||
});
|
||||
});
|
||||
|
||||
group('update NSPrincipalClass to FlutterApplication', () {
|
||||
late MemoryFileSystem memoryFileSystem;
|
||||
late BufferLogger testLogger;
|
||||
late FakeMacOSProject project;
|
||||
late File infoPlistFile;
|
||||
late FakePlistParser fakePlistParser;
|
||||
late FlutterProjectFactory flutterProjectFactory;
|
||||
|
||||
setUp(() {
|
||||
memoryFileSystem = MemoryFileSystem();
|
||||
fakePlistParser = FakePlistParser();
|
||||
testLogger = BufferLogger.test();
|
||||
project = FakeMacOSProject();
|
||||
infoPlistFile = memoryFileSystem.file('Info.plist');
|
||||
project.defaultHostInfoPlist = infoPlistFile;
|
||||
flutterProjectFactory = FlutterProjectFactory(
|
||||
fileSystem: memoryFileSystem,
|
||||
logger: testLogger,
|
||||
);
|
||||
});
|
||||
|
||||
void testWithMocks(String description, Future<void> Function() testMethod) {
|
||||
testUsingContext(description, testMethod, overrides: <Type, Generator>{
|
||||
FileSystem: () => memoryFileSystem,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
PlistParser: () => fakePlistParser,
|
||||
FlutterProjectFactory: () => flutterProjectFactory,
|
||||
});
|
||||
}
|
||||
|
||||
testWithMocks('skipped if files are missing', () async {
|
||||
final FlutterApplicationMigration macOSProjectMigration = FlutterApplicationMigration(
|
||||
project,
|
||||
testLogger,
|
||||
);
|
||||
macOSProjectMigration.migrate();
|
||||
expect(infoPlistFile.existsSync(), isFalse);
|
||||
|
||||
expect(testLogger.traceText, contains('${infoPlistFile.basename} not found, skipping FlutterApplication migration.'));
|
||||
expect(testLogger.statusText, isEmpty);
|
||||
});
|
||||
|
||||
testWithMocks('skipped if no NSPrincipalClass key exists to upgrade', () async {
|
||||
final FlutterApplicationMigration macOSProjectMigration = FlutterApplicationMigration(
|
||||
project,
|
||||
testLogger,
|
||||
);
|
||||
infoPlistFile.writeAsStringSync('contents'); // Just so it exists: parser is a fake.
|
||||
macOSProjectMigration.migrate();
|
||||
expect(fakePlistParser.getStringValueFromFile(infoPlistFile.path, PlistParser.kNSPrincipalClassKey), isNull);
|
||||
expect(testLogger.statusText, isEmpty);
|
||||
});
|
||||
|
||||
testWithMocks('skipped if already upgraded', () async {
|
||||
fakePlistParser.setProperty(PlistParser.kNSPrincipalClassKey, 'FlutterApplication');
|
||||
final FlutterApplicationMigration macOSProjectMigration = FlutterApplicationMigration(
|
||||
project,
|
||||
testLogger,
|
||||
);
|
||||
infoPlistFile.writeAsStringSync('contents'); // Just so it exists: parser is a fake.
|
||||
macOSProjectMigration.migrate();
|
||||
expect(fakePlistParser.getStringValueFromFile(infoPlistFile.path, PlistParser.kNSPrincipalClassKey), 'FlutterApplication');
|
||||
expect(testLogger.statusText, isEmpty);
|
||||
});
|
||||
|
||||
testWithMocks('Info.plist migrated to use FlutterApplication', () async {
|
||||
fakePlistParser.setProperty(PlistParser.kNSPrincipalClassKey, 'NSApplication');
|
||||
final FlutterApplicationMigration macOSProjectMigration = FlutterApplicationMigration(
|
||||
project,
|
||||
testLogger,
|
||||
);
|
||||
infoPlistFile.writeAsStringSync('contents'); // Just so it exists: parser is a fake.
|
||||
macOSProjectMigration.migrate();
|
||||
expect(fakePlistParser.getStringValueFromFile(infoPlistFile.path, PlistParser.kNSPrincipalClassKey), 'FlutterApplication');
|
||||
// Only print once.
|
||||
expect('Updating ${infoPlistFile.basename} to use FlutterApplication instead of NSApplication.'.allMatches(testLogger.statusText).length, 1);
|
||||
});
|
||||
|
||||
testWithMocks('Skip if NSPrincipalClass is not NSApplication', () async {
|
||||
const String differentApp = 'DIFFERENTApplication';
|
||||
fakePlistParser.setProperty(PlistParser.kNSPrincipalClassKey, differentApp);
|
||||
final FlutterApplicationMigration macOSProjectMigration = FlutterApplicationMigration(
|
||||
project,
|
||||
testLogger,
|
||||
);
|
||||
infoPlistFile.writeAsStringSync('contents'); // Just so it exists: parser is a fake.
|
||||
macOSProjectMigration.migrate();
|
||||
expect(fakePlistParser.getStringValueFromFile(infoPlistFile.path, PlistParser.kNSPrincipalClassKey), differentApp);
|
||||
expect(testLogger.traceText, contains('${infoPlistFile.basename} has an ${PlistParser.kNSPrincipalClassKey} of $differentApp, not NSApplication, skipping FlutterApplication migration'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class FakeMacOSProject extends Fake implements MacOSProject {
|
||||
@override
|
||||
File xcodeProjectInfoFile = MemoryFileSystem.test().file('xcodeProjectInfoFile');
|
||||
|
||||
@override
|
||||
File defaultHostInfoPlist = MemoryFileSystem.test().file('InfoplistFile');
|
||||
|
||||
@override
|
||||
File podfile = MemoryFileSystem.test().file('Podfile');
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ void main() {
|
||||
file.deleteSync();
|
||||
});
|
||||
|
||||
testWithoutContext('PlistParser.getStringValueFromFile works with xml file', () {
|
||||
testWithoutContext('PlistParser.getStringValueFromFile works with an XML file', () {
|
||||
file.writeAsBytesSync(base64.decode(base64PlistXml));
|
||||
|
||||
expect(parser.getStringValueFromFile(file.path, 'CFBundleIdentifier'), 'io.flutter.flutter.app');
|
||||
@ -83,7 +83,7 @@ void main() {
|
||||
expect(logger.errorText, isEmpty);
|
||||
}, skip: !platform.isMacOS); // [intended] requires macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.getStringValueFromFile works with binary file', () {
|
||||
testWithoutContext('PlistParser.getStringValueFromFile works with a binary file', () {
|
||||
file.writeAsBytesSync(base64.decode(base64PlistBinary));
|
||||
|
||||
expect(parser.getStringValueFromFile(file.path, 'CFBundleIdentifier'), 'io.flutter.flutter.app');
|
||||
@ -92,7 +92,7 @@ void main() {
|
||||
expect(logger.errorText, isEmpty);
|
||||
}, skip: !platform.isMacOS); // [intended] requires macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.getStringValueFromFile works with json file', () {
|
||||
testWithoutContext('PlistParser.getStringValueFromFile works with a JSON file', () {
|
||||
file.writeAsBytesSync(base64.decode(base64PlistJson));
|
||||
|
||||
expect(parser.getStringValueFromFile(file.path, 'CFBundleIdentifier'), 'io.flutter.flutter.app');
|
||||
@ -101,13 +101,13 @@ void main() {
|
||||
expect(logger.errorText, isEmpty);
|
||||
}, skip: !platform.isMacOS); // [intended] requires macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.getStringValueFromFile returns null for non-existent plist file', () {
|
||||
testWithoutContext('PlistParser.getStringValueFromFile returns null for a non-existent plist file', () {
|
||||
expect(parser.getStringValueFromFile('missing.plist', 'CFBundleIdentifier'), null);
|
||||
expect(logger.statusText, isEmpty);
|
||||
expect(logger.errorText, isEmpty);
|
||||
}, skip: !platform.isMacOS); // [intended] requires macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.getStringValueFromFile returns null for non-existent key within plist', () {
|
||||
testWithoutContext('PlistParser.getStringValueFromFile returns null for a non-existent key within a plist', () {
|
||||
file.writeAsBytesSync(base64.decode(base64PlistXml));
|
||||
|
||||
expect(parser.getStringValueFromFile(file.path, 'BadKey'), null);
|
||||
@ -116,12 +116,15 @@ void main() {
|
||||
expect(logger.errorText, isEmpty);
|
||||
}, skip: !platform.isMacOS); // [intended] requires macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.getStringValueFromFile returns null for malformed plist file', () {
|
||||
testWithoutContext('PlistParser.getStringValueFromFile returns null for a malformed plist file', () {
|
||||
file.writeAsBytesSync(const <int>[1, 2, 3, 4, 5, 6]);
|
||||
|
||||
expect(parser.getStringValueFromFile(file.path, 'CFBundleIdentifier'), null);
|
||||
expect(logger.statusText, isNotEmpty);
|
||||
expect(logger.errorText, isEmpty);
|
||||
expect(logger.statusText, contains('Property List error: Unexpected character \x01 at line 1 / '
|
||||
'JSON error: JSON text did not start with array or object and option to allow fragments not '
|
||||
'set. around line 1, column 0.\n'));
|
||||
expect(logger.errorText, 'ProcessException: The command failed\n'
|
||||
' Command: /usr/bin/plutil -convert xml1 -o - ${file.absolute.path}\n');
|
||||
}, skip: !platform.isMacOS); // [intended] requires macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.getStringValueFromFile throws when /usr/bin/plutil is not found', () async {
|
||||
@ -133,7 +136,68 @@ void main() {
|
||||
);
|
||||
expect(logger.statusText, isEmpty);
|
||||
expect(logger.errorText, isEmpty);
|
||||
}, skip: platform.isMacOS); // [intended] requires macos tool chain.
|
||||
}, skip: platform.isMacOS); // [intended] requires absence of macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.replaceKey can replace a key', () async {
|
||||
file.writeAsBytesSync(base64.decode(base64PlistXml));
|
||||
|
||||
expect(parser.getStringValueFromFile(file.path, 'CFBundleIdentifier'), 'io.flutter.flutter.app');
|
||||
expect(parser.replaceKey(file.path, key: 'CFBundleIdentifier', value: 'dev.flutter.fake'), isTrue);
|
||||
expect(logger.statusText, isEmpty);
|
||||
expect(logger.errorText, isEmpty);
|
||||
expect(parser.getStringValueFromFile(file.path, 'CFBundleIdentifier'), equals('dev.flutter.fake'));
|
||||
}, skip: !platform.isMacOS); // [intended] requires macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.replaceKey can create a new key', () async {
|
||||
file.writeAsBytesSync(base64.decode(base64PlistXml));
|
||||
|
||||
expect(parser.getStringValueFromFile(file.path, 'CFNewKey'), isNull);
|
||||
expect(parser.replaceKey(file.path, key: 'CFNewKey', value: 'dev.flutter.fake'), isTrue);
|
||||
expect(logger.statusText, isEmpty);
|
||||
expect(logger.errorText, isEmpty);
|
||||
expect(parser.getStringValueFromFile(file.path, 'CFNewKey'), equals('dev.flutter.fake'));
|
||||
}, skip: !platform.isMacOS); // [intended] requires macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.replaceKey can delete a key', () async {
|
||||
file.writeAsBytesSync(base64.decode(base64PlistXml));
|
||||
|
||||
expect(parser.replaceKey(file.path, key: 'CFBundleIdentifier'), isTrue);
|
||||
expect(logger.statusText, isEmpty);
|
||||
expect(logger.errorText, isEmpty);
|
||||
expect(parser.getStringValueFromFile(file.path, 'CFBundleIdentifier'), isNull);
|
||||
}, skip: !platform.isMacOS); // [intended] requires macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.replaceKey throws when /usr/bin/plutil is not found', () async {
|
||||
file.writeAsBytesSync(base64.decode(base64PlistXml));
|
||||
|
||||
expect(
|
||||
() => parser.replaceKey(file.path, key: 'CFBundleIdentifier', value: 'dev.flutter.fake'),
|
||||
throwsA(isA<FileNotFoundException>()),
|
||||
);
|
||||
expect(logger.statusText, isEmpty);
|
||||
expect(logger.errorText, isEmpty);
|
||||
}, skip: platform.isMacOS); // [intended] requires absence of macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.replaceKey returns false for a malformed plist file', () {
|
||||
file.writeAsBytesSync(const <int>[1, 2, 3, 4, 5, 6]);
|
||||
|
||||
expect(parser.replaceKey(file.path, key: 'CFBundleIdentifier', value: 'dev.flutter.fake'), isFalse);
|
||||
expect(logger.statusText, contains('foo.plist: Property List error: Unexpected character \x01 '
|
||||
'at line 1 / JSON error: JSON text did not start with array or object and option to allow '
|
||||
'fragments not set. around line 1, column 0.\n'));
|
||||
expect(logger.errorText, equals('ProcessException: The command failed\n'
|
||||
' Command: /usr/bin/plutil -replace CFBundleIdentifier -string dev.flutter.fake foo.plist\n'));
|
||||
}, skip: !platform.isMacOS); // [intended] requires macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.replaceKey works with a JSON file', () {
|
||||
file.writeAsBytesSync(base64.decode(base64PlistJson));
|
||||
|
||||
expect(parser.getStringValueFromFile(file.absolute.path, 'CFBundleIdentifier'), 'io.flutter.flutter.app');
|
||||
expect(parser.replaceKey(file.path, key:'CFBundleIdentifier', value: 'dev.flutter.fake'), isTrue);
|
||||
expect(parser.getStringValueFromFile(file.absolute.path, 'CFBundleIdentifier'), 'dev.flutter.fake');
|
||||
expect(logger.statusText, isEmpty);
|
||||
expect(logger.errorText, isEmpty);
|
||||
}, skip: !platform.isMacOS); // [intended] requires macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.parseFile can handle different datatypes', () async {
|
||||
file.writeAsBytesSync(base64.decode(base64PlistXmlWithComplexDatatypes));
|
||||
|
||||
@ -298,6 +298,16 @@ class FakePlistParser implements PlistParser {
|
||||
String? getStringValueFromFile(String plistFilePath, String key) {
|
||||
return _underlyingValues[key] as String?;
|
||||
}
|
||||
|
||||
@override
|
||||
bool replaceKey(String plistFilePath, {required String key, String? value}) {
|
||||
if (value == null) {
|
||||
_underlyingValues.remove(key);
|
||||
return true;
|
||||
}
|
||||
setProperty(key, value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class FakeBotDetector implements BotDetector {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user