// 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/memory.dart'; import 'package:flutter_tools/src/android/deferred_components_prebuild_validator.dart'; import 'package:flutter_tools/src/android/deferred_components_validator.dart'; import 'package:flutter_tools/src/base/deferred_component.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import '../../src/common.dart'; import '../../src/context.dart'; void main() { late FileSystem fileSystem; late BufferLogger logger; late Directory projectDir; late Platform platform; late Directory flutterRootDir; setUp(() { fileSystem = MemoryFileSystem.test(); logger = BufferLogger.test(); projectDir = fileSystem.directory('/project'); flutterRootDir = fileSystem.directory('/flutter_root'); platform = FakePlatform(); }); testWithoutContext('No checks passes', () async { final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator( projectDir, logger, platform, exitOnFail: false, title: 'test check', ); validator.displayResults(); validator.attemptToolExit(); expect(logger.statusText, 'test check passed.\n'); }); testWithoutContext('clearTempDir passes', () async { final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator( projectDir, logger, platform, exitOnFail: false, title: 'test check', ); validator.displayResults(); validator.attemptToolExit(); expect(logger.statusText, 'test check passed.\n'); }); testUsingContext( 'androidComponentSetup build.gradle does not exist', () async { final Directory templatesDir = flutterRootDir.childDirectory('templates'); final Directory deferredComponentDir = templatesDir .childDirectory('module') .childDirectory('android') .childDirectory('deferred_component'); final File buildGradleTemplate = deferredComponentDir.childFile('build.gradle.tmpl'); final File androidManifestTemplate = deferredComponentDir .childDirectory('src') .childDirectory('main') .childFile('AndroidManifest.xml.tmpl'); deferredComponentDir.createSync(recursive: true); buildGradleTemplate.createSync(recursive: true); androidManifestTemplate.createSync(recursive: true); buildGradleTemplate.writeAsStringSync( 'fake build.gradle template {{componentName}}', flush: true, mode: FileMode.append, ); androidManifestTemplate.writeAsStringSync( 'fake AndroidManifest.xml template {{componentName}}', flush: true, mode: FileMode.append, ); final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator( projectDir, logger, platform, exitOnFail: false, title: 'test check', templatesDir: templatesDir, ); final Directory componentDir = projectDir .childDirectory('android') .childDirectory('component1'); final File file = componentDir .childDirectory('src') .childDirectory('main') .childFile('AndroidManifest.xml'); if (file.existsSync()) { file.deleteSync(); } file.createSync(recursive: true); await validator.checkAndroidDynamicFeature([ DeferredComponent(name: 'component1'), ]); validator.displayResults(); validator.attemptToolExit(); file.deleteSync(); expect(logger.statusText.contains('Newly generated android files:\n'), true); expect( logger.statusText.contains( 'build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/component1/build.gradle\n', ), true, ); }, overrides: { FileSystem: () => fileSystem, ProcessManager: () => FakeProcessManager.any(), }, ); testUsingContext( 'androidComponentSetup AndroidManifest.xml does not exist', () async { final Directory templatesDir = flutterRootDir.childDirectory('templates'); final Directory deferredComponentDir = templatesDir .childDirectory('module') .childDirectory('android') .childDirectory('deferred_component'); final File buildGradleTemplate = deferredComponentDir.childFile('build.gradle.tmpl'); final File androidManifestTemplate = deferredComponentDir .childDirectory('src') .childDirectory('main') .childFile('AndroidManifest.xml.tmpl'); deferredComponentDir.createSync(recursive: true); buildGradleTemplate.createSync(recursive: true); androidManifestTemplate.createSync(recursive: true); buildGradleTemplate.writeAsStringSync( 'fake build.gradle template {{componentName}}', flush: true, mode: FileMode.append, ); androidManifestTemplate.writeAsStringSync( 'fake AndroidManifest.xml template {{componentName}}', flush: true, mode: FileMode.append, ); final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator( projectDir, logger, platform, exitOnFail: false, title: 'test check', templatesDir: templatesDir, ); final Directory componentDir = projectDir .childDirectory('android') .childDirectory('component1'); final File file = componentDir.childFile('build.gradle'); if (file.existsSync()) { file.deleteSync(); } file.createSync(recursive: true); await validator.checkAndroidDynamicFeature([ DeferredComponent(name: 'component1'), ]); validator.displayResults(); validator.attemptToolExit(); file.deleteSync(); expect(logger.statusText.contains('Newly generated android files:\n'), true); expect( logger.statusText.contains( 'build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/component1/src/main/AndroidManifest.xml\n', ), true, ); }, overrides: { FileSystem: () => fileSystem, ProcessManager: () => FakeProcessManager.any(), }, ); testWithoutContext('androidComponentSetup all files exist passes', () async { final Directory templatesDir = flutterRootDir .childDirectory('templates') .childDirectory('deferred_component'); final File buildGradleTemplate = templatesDir.childFile('build.gradle.tmpl'); final File androidManifestTemplate = templatesDir .childDirectory('src') .childDirectory('main') .childFile('AndroidManifest.xml.tmpl'); if (templatesDir.existsSync()) { templatesDir.deleteSync(recursive: true); } buildGradleTemplate.createSync(recursive: true); androidManifestTemplate.createSync(recursive: true); buildGradleTemplate.writeAsStringSync( 'fake build.gradle template {{componentName}}', flush: true, mode: FileMode.append, ); androidManifestTemplate.writeAsStringSync( 'fake AndroidManifest.xml template {{componentName}}', flush: true, mode: FileMode.append, ); final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator( projectDir, logger, platform, exitOnFail: false, title: 'test check', templatesDir: templatesDir, ); final Directory componentDir = projectDir .childDirectory('android') .childDirectory('component1'); final File buildGradle = componentDir.childFile('build.gradle'); if (buildGradle.existsSync()) { buildGradle.deleteSync(); } buildGradle.createSync(recursive: true); final File manifest = componentDir .childDirectory('src') .childDirectory('main') .childFile('AndroidManifest.xml'); if (manifest.existsSync()) { manifest.deleteSync(); } manifest.createSync(recursive: true); await validator.checkAndroidDynamicFeature([ DeferredComponent(name: 'component1'), ]); validator.displayResults(); validator.attemptToolExit(); manifest.deleteSync(); buildGradle.deleteSync(); expect(logger.statusText, 'test check passed.\n'); }); testWithoutContext('androidStringMapping creates new file', () async { final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator( projectDir, logger, platform, exitOnFail: false, title: 'test check', ); final Directory baseModuleDir = projectDir.childDirectory('android').childDirectory('app'); final File stringRes = baseModuleDir .childDirectory('src') .childDirectory('main') .childDirectory('res') .childDirectory('values') .childFile('strings.xml'); if (stringRes.existsSync()) { stringRes.deleteSync(); } final File manifest = baseModuleDir .childDirectory('src') .childDirectory('main') .childFile('AndroidManifest.xml'); if (manifest.existsSync()) { manifest.deleteSync(); } manifest.createSync(recursive: true); manifest.writeAsStringSync( ''' ''', flush: true, mode: FileMode.append, ); validator.checkAndroidResourcesStrings([ DeferredComponent(name: 'component1', libraries: ['lib2']), DeferredComponent(name: 'component2', libraries: ['lib1', 'lib4']), ]); validator.displayResults(); validator.attemptToolExit(); expect(logger.statusText.contains('Modified android files:\n'), false); expect(logger.statusText.contains('Newly generated android files:\n'), true); expect( logger.statusText.contains( 'build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/app/src/main/res/values/strings.xml\n', ), true, ); final File stringsOutput = projectDir .childDirectory('build') .childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory) .childDirectory('app') .childDirectory('src') .childDirectory('main') .childDirectory('res') .childDirectory('values') .childFile('strings.xml'); expect(stringsOutput.existsSync(), true); expect( stringsOutput.readAsStringSync().contains( 'component1', ), true, ); expect( stringsOutput.readAsStringSync().contains( 'component2', ), true, ); }); testWithoutContext('androidStringMapping modifies strings file', () async { final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator( projectDir, logger, platform, exitOnFail: false, title: 'test check', ); final Directory baseModuleDir = projectDir.childDirectory('android').childDirectory('app'); final File stringRes = baseModuleDir .childDirectory('src') .childDirectory('main') .childDirectory('res') .childDirectory('values') .childFile('strings.xml'); if (stringRes.existsSync()) { stringRes.deleteSync(); } stringRes.createSync(recursive: true); stringRes.writeAsStringSync( ''' component1 ''', flush: true, mode: FileMode.append, ); validator.checkAndroidResourcesStrings([ DeferredComponent(name: 'component1', libraries: ['lib2']), DeferredComponent(name: 'component2', libraries: ['lib1', 'lib4']), ]); validator.displayResults(); validator.attemptToolExit(); expect(logger.statusText.contains('Newly generated android files:\n'), false); expect(logger.statusText.contains('Modified android files:\n'), true); expect( logger.statusText.contains( 'build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/app/src/main/res/values/strings.xml\n', ), true, ); final File stringsOutput = projectDir .childDirectory('build') .childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory) .childDirectory('app') .childDirectory('src') .childDirectory('main') .childDirectory('res') .childDirectory('values') .childFile('strings.xml'); expect(stringsOutput.existsSync(), true); expect( stringsOutput.readAsStringSync().contains( 'component1', ), true, ); expect( stringsOutput.readAsStringSync().contains( 'component2', ), true, ); }); }