diff --git a/packages/flutter_tools/lib/src/commands/widget_preview.dart b/packages/flutter_tools/lib/src/commands/widget_preview.dart index 692fe59aee..d83930dd85 100644 --- a/packages/flutter_tools/lib/src/commands/widget_preview.dart +++ b/packages/flutter_tools/lib/src/commands/widget_preview.dart @@ -17,11 +17,8 @@ import '../flutter_manifest.dart'; import '../globals.dart' as globals; import '../project.dart'; import '../runner/flutter_command.dart'; -import '../widget_preview/preview_code_generator.dart'; -import '../widget_preview/preview_detector.dart'; import 'create_base.dart'; -// TODO(bkonyi): use dependency injection instead of global accessors throughout this file. class WidgetPreviewCommand extends FlutterCommand { WidgetPreviewCommand() { addSubcommand(WidgetPreviewStartCommand()); @@ -86,13 +83,6 @@ class WidgetPreviewStartCommand extends FlutterCommand @override String get name => 'start'; - late final PreviewDetector _previewDetector = PreviewDetector( - logger: globals.logger, - onChangeDetected: onChangeDetected, - ); - - late final PreviewCodeGenerator _previewCodeGenerator; - @override Future runCommand() async { final FlutterProject rootProject = getRootProject(); @@ -122,26 +112,9 @@ class WidgetPreviewStartCommand extends FlutterCommand ); await _populatePreviewPubspec(rootProject: rootProject); } - - // WARNING: this needs to happen after we generate the scaffold project as invoking the - // widgetPreviewScaffoldProject getter triggers lazy initialization of the preview scaffold's - // FlutterManifest before the scaffold project's pubspec has been generated. - _previewCodeGenerator = PreviewCodeGenerator( - widgetPreviewScaffoldProject: rootProject.widgetPreviewScaffoldProject, - fs: globals.fs, - ); - - final PreviewMapping initialPreviews = await _previewDetector.initialize(rootProject.directory); - _previewCodeGenerator.populatePreviewsInGeneratedPreviewScaffold(initialPreviews); - - await _previewDetector.dispose(); return FlutterCommandResult.success(); } - void onChangeDetected(PreviewMapping previews) { - // TODO(bkonyi): perform hot reload - } - @visibleForTesting static const Map flutterGenPackageConfigEntry = { 'name': 'flutter_gen', diff --git a/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart b/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart deleted file mode 100644 index 18486394ea..0000000000 --- a/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart +++ /dev/null @@ -1,76 +0,0 @@ -// 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:code_builder/code_builder.dart'; - -import '../base/file_system.dart'; -import '../project.dart'; -import 'preview_detector.dart'; - -/// Generates the Dart source responsible for importing widget previews from the developer's project -/// into the widget preview scaffold. -class PreviewCodeGenerator { - PreviewCodeGenerator({required this.widgetPreviewScaffoldProject, required this.fs}); - - final FileSystem fs; - - /// The project for the widget preview scaffold found under `.dart_tool/` in the developer's - /// project. - final FlutterProject widgetPreviewScaffoldProject; - - static const String generatedPreviewFilePath = 'lib/generated_preview.dart'; - - /// Generates code used by the widget preview scaffold based on the preview instances listed in - /// [previews]. - /// - /// The generated file will contain a single top level function named `previews()` which returns - /// a `List` that contains each widget preview defined in [previews]. - /// - /// An example of a formatted generated file containing previews from two files could be: - /// - /// ```dart - /// import 'package:foo/foo.dart' as _i1; - /// import 'package:foo/src/bar.dart' as _i2; - /// import 'package:widget_preview/widget_preview.dart'; - /// - /// List previews() => [ - /// _i1.fooPreview(), - /// _i2.barPreview1(), - /// _i3.barPreview2(), - /// ]; - /// ``` - void populatePreviewsInGeneratedPreviewScaffold(PreviewMapping previews) { - final Library lib = Library( - (LibraryBuilder b) => b.body.addAll([ - Directive.import( - // TODO(bkonyi): update with actual location in the framework - 'package:widget_preview/widget_preview.dart', - ), - Method( - (MethodBuilder b) => - b - ..body = - literalList([ - for (final MapEntry>( - key: String path, - value: List previewMethods, - ) - in previews.entries) ...[ - for (final String method in previewMethods) - refer(method, path).call([]), - ], - ]).code - ..name = 'previews' - ..returns = refer('List'), - ), - ]), - ); - final DartEmitter emitter = DartEmitter.scoped(useNullSafetySyntax: true); - final File generatedPreviewFile = fs.file( - widgetPreviewScaffoldProject.directory.uri.resolve(generatedPreviewFilePath), - ); - // TODO(bkonyi): do we want to bother with formatting this? - generatedPreviewFile.writeAsStringSync(lib.accept(emitter).toString()); - } -} diff --git a/packages/flutter_tools/lib/src/widget_preview/preview_detector.dart b/packages/flutter_tools/lib/src/widget_preview/preview_detector.dart deleted file mode 100644 index 70d01df3ed..0000000000 --- a/packages/flutter_tools/lib/src/widget_preview/preview_detector.dart +++ /dev/null @@ -1,145 +0,0 @@ -// 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:async'; - -// ignore: implementation_imports -import 'package:_fe_analyzer_shared/src/base/syntactic_entity.dart'; -import 'package:analyzer/dart/analysis/analysis_context.dart'; -import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; -import 'package:analyzer/dart/analysis/results.dart'; -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/file_system/physical_file_system.dart'; -import 'package:watcher/watcher.dart'; - -import '../base/file_system.dart'; -import '../base/logger.dart'; -import '../base/utils.dart'; -import '../globals.dart' as globals; -import 'preview_code_generator.dart'; - -typedef PreviewMapping = Map>; - -class PreviewDetector { - PreviewDetector({required this.logger, required this.onChangeDetected}); - - final Logger logger; - final void Function(PreviewMapping) onChangeDetected; - StreamSubscription? _fileWatcher; - late final PreviewMapping _pathToPreviews; - - /// Starts listening for changes to Dart sources under [projectRoot] and returns - /// the initial [PreviewMapping] for the project. - Future initialize(Directory projectRoot) async { - // Find the initial set of previews. - _pathToPreviews = findPreviewFunctions(projectRoot); - - final Watcher watcher = Watcher(projectRoot.path); - // TODO(bkonyi): watch for changes to pubspec.yaml - _fileWatcher = watcher.events.listen((WatchEvent event) async { - final String eventPath = Uri.file(event.path).toString(); - // Only trigger a reload when changes to Dart sources are detected. We - // ignore the generated preview file to avoid getting stuck in a loop. - if (!eventPath.endsWith('.dart') || - eventPath.endsWith(PreviewCodeGenerator.generatedPreviewFilePath)) { - return; - } - logger.printStatus('Detected change in $eventPath.'); - final PreviewMapping filePreviewsMapping = findPreviewFunctions( - globals.fs.file(Uri.file(event.path)), - ); - if (filePreviewsMapping.isEmpty && !_pathToPreviews.containsKey(eventPath)) { - // No previews found or removed, nothing to do. - return; - } - if (filePreviewsMapping.length > 1) { - logger.printWarning('Previews from more than one file were detected!'); - logger.printWarning('Previews: $filePreviewsMapping'); - } - if (filePreviewsMapping.isNotEmpty) { - // The set of previews has changed, but there are still previews in the file. - final MapEntry>(key: String uri, value: List filePreviews) = - filePreviewsMapping.entries.first; - assert(uri == eventPath); - logger.printStatus('Updated previews for $eventPath: $filePreviews'); - if (filePreviews.isNotEmpty) { - final List? currentPreviewsForFile = _pathToPreviews[eventPath]; - if (filePreviews != currentPreviewsForFile) { - _pathToPreviews[eventPath] = filePreviews; - } - } - } else { - // The file previously had previews that were removed. - logger.printStatus('Previews removed from $eventPath'); - _pathToPreviews.remove(eventPath); - } - onChangeDetected(_pathToPreviews); - }); - // Wait for file watcher to finish initializing, otherwise we might miss changes and cause - // tests to flake. - await watcher.ready; - return _pathToPreviews; - } - - Future dispose() async { - await _fileWatcher?.cancel(); - } - - /// Search for functions annotated with `@Preview` in the current project. - PreviewMapping findPreviewFunctions(FileSystemEntity entity) { - final AnalysisContextCollection collection = AnalysisContextCollection( - includedPaths: [entity.absolute.path], - resourceProvider: PhysicalResourceProvider.INSTANCE, - ); - - final PreviewMapping previews = PreviewMapping(); - for (final AnalysisContext context in collection.contexts) { - logger.printStatus('Finding previews in ${context.contextRoot.root.path}...'); - - for (final String filePath in context.contextRoot.analyzedFiles()) { - logger.printTrace('Checking file: $filePath'); - if (!filePath.endsWith('.dart')) { - continue; - } - - final SomeParsedLibraryResult lib = context.currentSession.getParsedLibrary(filePath); - if (lib is ParsedLibraryResult) { - for (final ParsedUnitResult unit in lib.units) { - final List previewEntries = previews[unit.uri.toString()] ?? []; - for (final SyntacticEntity entity in unit.unit.childEntities) { - if (entity is FunctionDeclaration && !entity.name.toString().startsWith('_')) { - bool foundPreview = false; - for (final Annotation annotation in entity.metadata) { - if (annotation.name.name == 'Preview') { - // What happens if the annotation is applied multiple times? - foundPreview = true; - break; - } - } - if (foundPreview) { - logger.printStatus('Found preview at:'); - logger.printStatus('File path: ${unit.uri}'); - logger.printStatus('Preview function: ${entity.name}'); - logger.printStatus(''); - previewEntries.add(entity.name.toString()); - } - } - } - if (previewEntries.isNotEmpty) { - previews[unit.uri.toString()] = previewEntries; - } - } - } else { - logger.printWarning('Unknown library type at $filePath: $lib'); - } - } - } - final int previewCount = previews.values.fold( - 0, - (int count, List value) => count + value.length, - ); - logger.printStatus('Found $previewCount ${pluralize('preview', previewCount)}.'); - return previews; - } -} diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index df806ee6d5..642eecc8ed 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -14,7 +14,6 @@ dependencies: args: 2.6.0 dds: 5.0.0 dwds: 24.3.3 - code_builder: 4.10.1 completion: 1.0.1 coverage: 1.11.1 crypto: 3.0.6 @@ -122,4 +121,4 @@ dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: a49f +# PUBSPEC CHECKSUM: cf3c diff --git a/packages/flutter_tools/test/commands.shard/permeable/widget_preview_test.dart b/packages/flutter_tools/test/commands.shard/permeable/widget_preview_test.dart index 87c4172f23..3e2c10d34b 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/widget_preview_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/widget_preview_test.dart @@ -11,7 +11,6 @@ import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/commands/widget_preview.dart'; import 'package:flutter_tools/src/dart/pub.dart'; import 'package:flutter_tools/src/globals.dart' as globals; -import 'package:flutter_tools/src/widget_preview/preview_code_generator.dart'; import '../../src/common.dart'; import '../../src/context.dart'; @@ -34,12 +33,8 @@ void main() { tryToDelete(tempDir); }); - Future createRootProject() async { - return globals.fs.directory(await createProject(tempDir, arguments: ['--pub'])); - } - - Directory widgetPreviewScaffoldFromRootProject({required Directory rootProject}) { - return rootProject.childDirectory('.dart_tool').childDirectory('widget_preview_scaffold'); + Future createRootProject() async { + return createProject(tempDir, arguments: ['--pub']); } Future runWidgetPreviewCommand(List arguments) async { @@ -48,29 +43,28 @@ void main() { } Future startWidgetPreview({ - required Directory? rootProject, + required String? rootProjectPath, List? arguments, }) async { await runWidgetPreviewCommand([ 'start', ...?arguments, - if (rootProject != null) rootProject.path, + if (rootProjectPath != null) rootProjectPath, ]); - final Directory widgetPreviewScaffoldDir = widgetPreviewScaffoldFromRootProject( - rootProject: rootProject ?? globals.fs.currentDirectory, - ); - expect(widgetPreviewScaffoldDir, exists); expect( - widgetPreviewScaffoldDir.childFile(PreviewCodeGenerator.generatedPreviewFilePath), + globals.fs + .directory(rootProjectPath ?? globals.fs.currentDirectory.path) + .childDirectory('.dart_tool') + .childDirectory('widget_preview_scaffold'), exists, ); } - Future cleanWidgetPreview({required Directory rootProject}) async { - await runWidgetPreviewCommand(['clean', rootProject.path]); + Future cleanWidgetPreview({required String rootProjectPath}) async { + await runWidgetPreviewCommand(['clean', rootProjectPath]); expect( globals.fs - .directory(rootProject) + .directory(rootProjectPath) .childDirectory('.dart_tool') .childDirectory('widget_preview_scaffold'), isNot(exists), @@ -99,7 +93,7 @@ void main() { testUsingContext('run outside of a Flutter project directory', () async { try { - await startWidgetPreview(rootProject: tempDir); + await startWidgetPreview(rootProjectPath: tempDir.path); fail('Successfully executed outside of a Flutter project directory'); } on ToolExit catch (e) { expect(e.message, contains('${tempDir.path} is not a valid Flutter project.')); @@ -110,8 +104,8 @@ void main() { testUsingContext( 'start creates .dart_tool/widget_preview_scaffold', () async { - final Directory rootProject = await createRootProject(); - await startWidgetPreview(rootProject: rootProject); + final String rootProjectPath = await createRootProject(); + await startWidgetPreview(rootProjectPath: rootProjectPath); }, overrides: { Pub: @@ -129,89 +123,11 @@ void main() { testUsingContext( 'start creates .dart_tool/widget_preview_scaffold in the CWD', () async { - final Directory rootProject = await createRootProject(); + final String rootProjectPath = await createRootProject(); await io.IOOverrides.runZoned>(() async { // Try to execute using the CWD. - await startWidgetPreview(rootProject: null); - }, getCurrentDirectory: () => rootProject); - }, - overrides: { - Pub: - () => Pub.test( - fileSystem: globals.fs, - logger: globals.logger, - processManager: globals.processManager, - usage: globals.flutterUsage, - botDetector: globals.botDetector, - platform: globals.platform, - stdio: mockStdio, - ), - }, - ); - - const String samplePreviewFile = ''' -// This doesn't need to be valid code for testing as long as it has the @Preview() annotation -@Preview() -WidgetPreview preview() => WidgetPreview();'''; - - const String expectedGeneratedFileContents = ''' -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:flutter_project/foo.dart' as _i1;import 'package:widget_preview/widget_preview.dart';List previews() => [_i1.preview()];'''; - - testUsingContext( - 'start finds existing previews and injects them into ${PreviewCodeGenerator.generatedPreviewFilePath}', - () async { - final Directory rootProject = await createRootProject(); - final Directory widgetPreviewScaffoldDir = widgetPreviewScaffoldFromRootProject( - rootProject: rootProject, - ); - rootProject - .childDirectory('lib') - .childFile('foo.dart') - .writeAsStringSync(samplePreviewFile); - - final File generatedFile = widgetPreviewScaffoldDir.childFile( - PreviewCodeGenerator.generatedPreviewFilePath, - ); - - await startWidgetPreview(rootProject: rootProject); - expect(generatedFile.readAsStringSync(), expectedGeneratedFileContents); - }, - overrides: { - Pub: - () => Pub.test( - fileSystem: globals.fs, - logger: globals.logger, - processManager: globals.processManager, - usage: globals.flutterUsage, - botDetector: globals.botDetector, - platform: globals.platform, - stdio: mockStdio, - ), - }, - ); - - testUsingContext( - 'start finds existing previews in the CWD and injects them into ${PreviewCodeGenerator.generatedPreviewFilePath}', - () async { - final Directory rootProject = await createRootProject(); - final Directory widgetPreviewScaffoldDir = widgetPreviewScaffoldFromRootProject( - rootProject: rootProject, - ); - rootProject - .childDirectory('lib') - .childFile('foo.dart') - .writeAsStringSync(samplePreviewFile); - - final File generatedFile = widgetPreviewScaffoldDir.childFile( - PreviewCodeGenerator.generatedPreviewFilePath, - ); - - await io.IOOverrides.runZoned>(() async { - // Try to execute using the CWD. - await startWidgetPreview(rootProject: null); - expect(generatedFile.readAsStringSync(), expectedGeneratedFileContents); - }, getCurrentDirectory: () => globals.fs.directory(rootProject)); + await startWidgetPreview(rootProjectPath: null); + }, getCurrentDirectory: () => globals.fs.directory(rootProjectPath)); }, overrides: { Pub: @@ -229,9 +145,9 @@ import 'package:flutter_project/foo.dart' as _i1;import 'package:widget_preview/ testUsingContext( 'clean deletes .dart_tool/widget_preview_scaffold', () async { - final Directory rootProject = await createRootProject(); - await startWidgetPreview(rootProject: rootProject); - await cleanWidgetPreview(rootProject: rootProject); + final String rootProjectPath = await createRootProject(); + await startWidgetPreview(rootProjectPath: rootProjectPath); + await cleanWidgetPreview(rootProjectPath: rootProjectPath); }, overrides: { Pub: @@ -250,12 +166,12 @@ import 'package:flutter_project/foo.dart' as _i1;import 'package:widget_preview/ 'invokes pub in online and offline modes', () async { // Run pub online first in order to populate the pub cache. - final Directory rootProject = await createRootProject(); + final String rootProjectPath = await createRootProject(); loggingProcessManager.clear(); final RegExp dartCommand = RegExp(r'dart-sdk[\\/]bin[\\/]dart'); - await startWidgetPreview(rootProject: rootProject); + await startWidgetPreview(rootProjectPath: rootProjectPath); expect( loggingProcessManager.commands, contains( @@ -266,12 +182,12 @@ import 'package:flutter_project/foo.dart' as _i1;import 'package:widget_preview/ ), ); - await cleanWidgetPreview(rootProject: rootProject); + await cleanWidgetPreview(rootProjectPath: rootProjectPath); // Run pub offline. loggingProcessManager.clear(); await startWidgetPreview( - rootProject: rootProject, + rootProjectPath: rootProjectPath, arguments: ['--pub', '--offline'], ); diff --git a/packages/flutter_tools/test/general.shard/widget_preview/preview_code_generator_test.dart b/packages/flutter_tools/test/general.shard/widget_preview/preview_code_generator_test.dart deleted file mode 100644 index e5e12edd97..0000000000 --- a/packages/flutter_tools/test/general.shard/widget_preview/preview_code_generator_test.dart +++ /dev/null @@ -1,74 +0,0 @@ -// 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:file_testing/file_testing.dart'; -import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/flutter_manifest.dart'; -import 'package:flutter_tools/src/project.dart'; -import 'package:flutter_tools/src/widget_preview/preview_code_generator.dart'; -import 'package:test/test.dart'; - -import '../../src/context.dart'; - -void main() { - group('$PreviewCodeGenerator', () { - late PreviewCodeGenerator codeGenerator; - late FlutterProject project; - - setUp(() { - final FileSystem fs = MemoryFileSystem.test(); - final FlutterManifest manifest = FlutterManifest.empty(logger: BufferLogger.test()); - final Directory projectDir = - fs.currentDirectory.childDirectory('project') - ..createSync() - ..childDirectory('lib').createSync(); - project = FlutterProject(projectDir, manifest, manifest); - codeGenerator = PreviewCodeGenerator(widgetPreviewScaffoldProject: project, fs: fs); - }); - - testUsingContext( - 'correctly generates ${PreviewCodeGenerator.generatedPreviewFilePath}', - () async { - // Check that the generated preview file doesn't exist yet. - final File generatedPreviewFile = project.directory.childFile( - PreviewCodeGenerator.generatedPreviewFilePath, - ); - expect(generatedPreviewFile, isNot(exists)); - - // Populate the generated preview file. - codeGenerator.populatePreviewsInGeneratedPreviewScaffold(const >{ - 'foo.dart': ['preview'], - 'src/bar.dart': ['barPreview1', 'barPreview2'], - }); - expect(generatedPreviewFile, exists); - - // Check that the generated file contains: - // - An import of the widget preview library - // - Prefixed imports for both 'foo.dart' and 'src/bar.dart' - // - A top-level function 'List previews()' - // - A returned list containing function calls to 'preview()' from 'foo.dart' and 'barPreview1()' - // and 'barPreview2()' from 'src/bar.dart' - // - // The generated file is unfortunately unformatted. - const String expectedGeneratedPreviewFileContents = ''' -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'foo.dart' as _i1;import 'src/bar.dart' as _i2;import 'package:widget_preview/widget_preview.dart';List previews() => [_i1.preview(), _i2.barPreview1(), _i2.barPreview2(), ];'''; - expect(generatedPreviewFile.readAsStringSync(), expectedGeneratedPreviewFileContents); - - // Regenerate the generated file with no previews. - codeGenerator.populatePreviewsInGeneratedPreviewScaffold(const >{}); - expect(generatedPreviewFile, exists); - - // The generated file should only contain: - // - An import of the widget preview library - // - A top-level function 'List previews()' that returns an empty list. - const String emptyGeneratedPreviewFileContents = ''' -import 'package:widget_preview/widget_preview.dart';List previews() => [];'''; - expect(generatedPreviewFile.readAsStringSync(), emptyGeneratedPreviewFileContents); - }, - ); - }); -} diff --git a/packages/flutter_tools/test/general.shard/widget_preview/preview_detector_test.dart b/packages/flutter_tools/test/general.shard/widget_preview/preview_detector_test.dart deleted file mode 100644 index b00c70d07b..0000000000 --- a/packages/flutter_tools/test/general.shard/widget_preview/preview_detector_test.dart +++ /dev/null @@ -1,119 +0,0 @@ -// 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:async'; - -import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/base/signals.dart'; -import 'package:flutter_tools/src/widget_preview/preview_detector.dart'; -import 'package:test/test.dart'; - -import '../../src/common.dart'; -import '../../src/context.dart'; - -Directory createBasicProjectStructure(FileSystem fs) { - return fs.systemTempDirectory.createTempSync('root'); -} - -File addPreviewContainingFile(Directory projectRoot, String path) { - return projectRoot.childDirectory('lib').childFile(path) - ..createSync(recursive: true) - ..writeAsStringSync(previewContainingFileContents); -} - -File addNonPreviewContainingFile(Directory projectRoot, String path) { - return projectRoot.childDirectory('lib').childFile(path) - ..createSync(recursive: true) - ..writeAsStringSync(nonPreviewContainingFileContents); -} - -void main() { - group('$PreviewDetector', () { - // Note: we don't use a MemoryFileSystem since we don't have a way to - // provide it to package:analyzer APIs without writing a significant amount - // of wrapper logic. - late LocalFileSystem fs; - late Logger logger; - late PreviewDetector previewDetector; - late Directory projectRoot; - void Function(PreviewMapping)? onChangeDetected; - - void onChangeDetectedRoot(PreviewMapping mapping) { - onChangeDetected!(mapping); - } - - setUp(() { - fs = LocalFileSystem.test(signals: Signals.test()); - projectRoot = createBasicProjectStructure(fs); - logger = BufferLogger.test(); - previewDetector = PreviewDetector(logger: logger, onChangeDetected: onChangeDetectedRoot); - }); - - tearDown(() async { - await previewDetector.dispose(); - projectRoot.deleteSync(recursive: true); - onChangeDetected = null; - }); - - testUsingContext('can detect previews in existing files', () async { - final List previewFiles = [ - addPreviewContainingFile(projectRoot, 'foo.dart'), - addPreviewContainingFile(projectRoot, 'src/bar.dart'), - ]; - addNonPreviewContainingFile(projectRoot, 'baz.dart'); - final PreviewMapping mapping = previewDetector.findPreviewFunctions(projectRoot); - expect(mapping.keys.toSet(), previewFiles.map((File e) => e.uri.toString()).toSet()); - }); - - testUsingContext('can detect previews in updated files', () async { - // Create two files with existing previews and one without. - final PreviewMapping expectedInitialMapping = >{ - addPreviewContainingFile(projectRoot, 'foo.dart').uri.toString(): ['previews'], - addPreviewContainingFile(projectRoot, 'src/bar.dart').uri.toString(): ['previews'], - }; - final File nonPreviewContainingFile = addNonPreviewContainingFile(projectRoot, 'baz.dart'); - - Completer completer = Completer(); - onChangeDetected = (PreviewMapping updated) { - // The new preview in baz.dart should be included in the preview mapping. - expect(updated, >{ - ...expectedInitialMapping, - nonPreviewContainingFile.uri.toString(): ['previews'], - }); - completer.complete(); - }; - // Initialize the file watcher. - final PreviewMapping initialPreviews = await previewDetector.initialize(projectRoot); - expect(initialPreviews, expectedInitialMapping); - - // Update the file without an existing preview to include a preview and ensure it triggers - // the preview detector. - addPreviewContainingFile(projectRoot, 'baz.dart'); - await completer.future; - - completer = Completer(); - onChangeDetected = (PreviewMapping updated) { - // The removed preview in baz.dart should not longer be included in the preview mapping. - expect(updated, expectedInitialMapping); - completer.complete(); - }; - - // Update the file with an existing preview to remove the preview and ensure it triggers - // the preview detector. - addNonPreviewContainingFile(projectRoot, 'baz.dart'); - await completer.future; - }); - }); -} - -const String previewContainingFileContents = ''' -@Preview() -// This isn't necessarily valid code. We're just looking for the annotation -WidgetPreview previews() => WidgetPreview(); -'''; - -const String nonPreviewContainingFileContents = ''' -String foo() => 'bar'; -''';