// 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 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/base/project_migrator.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:meta/meta.dart'; import 'package:mockito/mockito.dart'; import '../../src/common.dart'; void main() { MockUsage mockUsage; MemoryFileSystem memoryFileSystem; BufferLogger testLogger; MockMacOSProject mockMacOSProject; File xcodeProjectInfoFile; setUp(() { mockUsage = MockUsage(); memoryFileSystem = MemoryFileSystem.test(); xcodeProjectInfoFile = memoryFileSystem.file('project.pbxproj'); testLogger = BufferLogger( terminal: AnsiTerminal( stdio: null, platform: const LocalPlatform(), ), outputPreferences: OutputPreferences.test(), ); mockMacOSProject = MockMacOSProject(); when(mockMacOSProject.xcodeProjectInfoFile) .thenReturn(xcodeProjectInfoFile); }); testWithoutContext('skipped if files are missing', () { final RemoveMacOSFrameworkLinkAndEmbeddingMigration macosProjectMigration = RemoveMacOSFrameworkLinkAndEmbeddingMigration( mockMacOSProject, testLogger, mockUsage, ); expect(macosProjectMigration.migrate(), isTrue); verifyNever(mockUsage.sendEvent( any, any, label: anyNamed('label'), value: anyNamed('value'), )); expect(xcodeProjectInfoFile.existsSync(), isFalse); expect( testLogger.traceText, contains( 'Xcode project not found, skipping framework link and embedding migration')); expect(testLogger.statusText, isEmpty); }); testWithoutContext('skipped if nothing to upgrade', () { const String contents = 'Nothing to upgrade'; xcodeProjectInfoFile.writeAsStringSync(contents); final DateTime projectLastModified = xcodeProjectInfoFile.lastModifiedSync(); final RemoveMacOSFrameworkLinkAndEmbeddingMigration macosProjectMigration = RemoveMacOSFrameworkLinkAndEmbeddingMigration( mockMacOSProject, testLogger, mockUsage, ); expect(macosProjectMigration.migrate(), isTrue); verifyNever(mockUsage.sendEvent( any, any, label: anyNamed('label'), value: anyNamed('value'), )); expect(xcodeProjectInfoFile.lastModifiedSync(), projectLastModified); expect(xcodeProjectInfoFile.readAsStringSync(), contents); expect(testLogger.statusText, isEmpty); }); testWithoutContext('skips migrating script with embed', () { const String contents = r''' shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; '''; xcodeProjectInfoFile.writeAsStringSync(contents); final RemoveMacOSFrameworkLinkAndEmbeddingMigration macosProjectMigration = RemoveMacOSFrameworkLinkAndEmbeddingMigration( mockMacOSProject, testLogger, mockUsage, ); expect(macosProjectMigration.migrate(), isTrue); expect(xcodeProjectInfoFile.readAsStringSync(), contents); expect(testLogger.statusText, isEmpty); }); testWithoutContext('Xcode project is migrated', () { xcodeProjectInfoFile.writeAsStringSync(r''' prefix D73912F022F37F9E000D13A0 D73912F222F3801D000D13A0 suffix D73912EF22F37F9E000D13A0 keep this 1 33D1A10422148B71006C7A3E spaces 33D1A10522148B93006C7A3E shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename\n"; keep this 2 '''); final RemoveMacOSFrameworkLinkAndEmbeddingMigration macosProjectMigration = RemoveMacOSFrameworkLinkAndEmbeddingMigration( mockMacOSProject, testLogger, mockUsage, ); expect(macosProjectMigration.migrate(), isTrue); verifyNever(mockUsage.sendEvent( any, any, label: anyNamed('label'), value: anyNamed('value'), )); expect(xcodeProjectInfoFile.readAsStringSync(), r''' keep this 1 shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; keep this 2 '''); expect(testLogger.statusText, contains('Upgrading project.pbxproj')); }); testWithoutContext('migration fails with leftover App.framework reference', () { xcodeProjectInfoFile.writeAsStringSync(''' D73912F022F37F9bogus /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D73912F022F37F9bogus /* App.framework */; }; '''); final RemoveMacOSFrameworkLinkAndEmbeddingMigration macosProjectMigration = RemoveMacOSFrameworkLinkAndEmbeddingMigration( mockMacOSProject, testLogger, mockUsage, ); expect(macosProjectMigration.migrate, throwsToolExit(message: 'Your Xcode project requires migration')); verify(mockUsage.sendEvent('macos-migration', 'remove-frameworks', label: 'failure', value: null)); }); testWithoutContext( 'migration fails with leftover FlutterMacOS.framework reference', () { xcodeProjectInfoFile.writeAsStringSync(''' 33D1A10522148B93bogus /* FlutterMacOS.framework in Bundle Framework */, '''); final RemoveMacOSFrameworkLinkAndEmbeddingMigration macosProjectMigration = RemoveMacOSFrameworkLinkAndEmbeddingMigration( mockMacOSProject, testLogger, mockUsage, ); expect(macosProjectMigration.migrate, throwsToolExit(message: 'Your Xcode project requires migration')); verify(mockUsage.sendEvent('macos-migration', 'remove-frameworks', label: 'failure', value: null)); }); } class MockMacOSProject extends Mock implements MacOSProject {} class MockUsage extends Mock implements Usage {} class FakeMacOSMigrator extends ProjectMigrator { FakeMacOSMigrator({@required this.succeeds}) : super(null); final bool succeeds; @override bool migrate() { return succeeds; } @override String migrateLine(String line) { return line; } }