|  | // Copyright 2013 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:args/command_runner.dart'; | 
|  | import 'package:file/file.dart'; | 
|  | import 'package:flutter_plugin_tools/src/common/core.dart'; | 
|  | import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; | 
|  | import 'package:flutter_plugin_tools/src/readme_check_command.dart'; | 
|  | import 'package:git/git.dart'; | 
|  | import 'package:test/test.dart'; | 
|  |  | 
|  | import 'mocks.dart'; | 
|  | import 'util.dart'; | 
|  |  | 
|  | void main() { | 
|  | late CommandRunner<void> runner; | 
|  | late MockPlatform mockPlatform; | 
|  | late Directory packagesDir; | 
|  |  | 
|  | setUp(() { | 
|  | mockPlatform = MockPlatform(); | 
|  | final RecordingProcessRunner processRunner; | 
|  | final GitDir gitDir; | 
|  | (:packagesDir, :processRunner, gitProcessRunner: _, :gitDir) = | 
|  | configureBaseCommandMocks(platform: mockPlatform); | 
|  | final ReadmeCheckCommand command = ReadmeCheckCommand( | 
|  | packagesDir, | 
|  | processRunner: processRunner, | 
|  | platform: mockPlatform, | 
|  | gitDir: gitDir, | 
|  | ); | 
|  |  | 
|  | runner = CommandRunner<void>( | 
|  | 'readme_check_command', 'Test for readme_check_command'); | 
|  | runner.addCommand(command); | 
|  | }); | 
|  |  | 
|  | test('prints paths of checked READMEs', () async { | 
|  | final RepositoryPackage package = createFakePackage( | 
|  | 'a_package', packagesDir, | 
|  | examples: <String>['example1', 'example2']); | 
|  | for (final RepositoryPackage example in package.getExamples()) { | 
|  | example.readmeFile.writeAsStringSync('A readme'); | 
|  | } | 
|  | getExampleDir(package).childFile('README.md').writeAsStringSync('A readme'); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['readme-check']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAll(<Matcher>[ | 
|  | contains('  Checking README.md...'), | 
|  | contains('  Checking example/README.md...'), | 
|  | contains('  Checking example/example1/README.md...'), | 
|  | contains('  Checking example/example2/README.md...'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('fails when package README is missing', () async { | 
|  | final RepositoryPackage package = | 
|  | createFakePackage('a_package', packagesDir); | 
|  | package.readmeFile.deleteSync(); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['readme-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder(<Matcher>[ | 
|  | contains('Missing README.md'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('passes when example README is missing', () async { | 
|  | createFakePackage('a_package', packagesDir); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['readme-check']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder(<Matcher>[ | 
|  | contains('No README for example'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('does not inculde non-example subpackages', () async { | 
|  | final RepositoryPackage package = | 
|  | createFakePackage('a_package', packagesDir); | 
|  | const String subpackageName = 'special_test'; | 
|  | final RepositoryPackage miscSubpackage = | 
|  | createFakePackage(subpackageName, package.directory); | 
|  | miscSubpackage.readmeFile.deleteSync(); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['readme-check']); | 
|  |  | 
|  | expect(output, isNot(contains(subpackageName))); | 
|  | }); | 
|  |  | 
|  | test('fails when README still has plugin template boilerplate', () async { | 
|  | final RepositoryPackage package = createFakePlugin('a_plugin', packagesDir); | 
|  | package.readmeFile.writeAsStringSync(''' | 
|  | ## Getting Started | 
|  |  | 
|  | This project is a starting point for a Flutter | 
|  | [plug-in package](https://flutter.dev/developing-packages/), | 
|  | a specialized package that includes platform-specific implementation code for | 
|  | Android and/or iOS. | 
|  |  | 
|  | For help getting started with Flutter development, view the | 
|  | [online documentation](https://docs.flutter.dev), which offers tutorials, | 
|  | samples, guidance on mobile development, and a full API reference. | 
|  | '''); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['readme-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder(<Matcher>[ | 
|  | contains('The boilerplate section about getting started with Flutter ' | 
|  | 'should not be left in.'), | 
|  | contains('Contains template boilerplate'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('fails when example README still has application template boilerplate', | 
|  | () async { | 
|  | final RepositoryPackage package = | 
|  | createFakePackage('a_package', packagesDir); | 
|  | package.getExamples().first.readmeFile.writeAsStringSync(''' | 
|  | ## Getting Started | 
|  |  | 
|  | This project is a starting point for a Flutter application. | 
|  |  | 
|  | A few resources to get you started if this is your first Flutter project: | 
|  |  | 
|  | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) | 
|  | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) | 
|  |  | 
|  | For help getting started with Flutter development, view the | 
|  | [online documentation](https://docs.flutter.dev/), which offers tutorials, | 
|  | samples, guidance on mobile development, and a full API reference. | 
|  | '''); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['readme-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder(<Matcher>[ | 
|  | contains('The boilerplate section about getting started with Flutter ' | 
|  | 'should not be left in.'), | 
|  | contains('Contains template boilerplate'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test( | 
|  | 'fails when a plugin implementation package example README has the ' | 
|  | 'template boilerplate', () async { | 
|  | final RepositoryPackage package = createFakePlugin( | 
|  | 'a_plugin_ios', packagesDir.childDirectory('a_plugin')); | 
|  | package.getExamples().first.readmeFile.writeAsStringSync(''' | 
|  | # a_plugin_ios_example | 
|  |  | 
|  | Demonstrates how to use the a_plugin_ios plugin. | 
|  | '''); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['readme-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder(<Matcher>[ | 
|  | contains('The boilerplate should not be left in for a federated plugin ' | 
|  | "implementation package's example."), | 
|  | contains('Contains template boilerplate'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test( | 
|  | 'allows the template boilerplate in the example README for packages ' | 
|  | 'other than plugin implementation packages', () async { | 
|  | final RepositoryPackage package = createFakePlugin( | 
|  | 'a_plugin', | 
|  | packagesDir.childDirectory('a_plugin'), | 
|  | platformSupport: <String, PlatformDetails>{ | 
|  | platformAndroid: const PlatformDetails(PlatformSupport.inline), | 
|  | }, | 
|  | ); | 
|  | // Write a README with an OS support table so that the main README check | 
|  | // passes. | 
|  | package.readmeFile.writeAsStringSync(''' | 
|  | # a_plugin | 
|  |  | 
|  | |                | Android | | 
|  | |----------------|---------| | 
|  | | **Support**    | SDK 19+ | | 
|  |  | 
|  | A great plugin. | 
|  | '''); | 
|  | package.getExamples().first.readmeFile.writeAsStringSync(''' | 
|  | # a_plugin_example | 
|  |  | 
|  | Demonstrates how to use the a_plugin plugin. | 
|  | '''); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['readme-check']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAll(<Matcher>[ | 
|  | contains('  Checking README.md...'), | 
|  | contains('  Checking example/README.md...'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test( | 
|  | 'fails when a plugin implementation package example README does not have ' | 
|  | 'the repo-standard message', () async { | 
|  | final RepositoryPackage package = createFakePlugin( | 
|  | 'a_plugin_ios', packagesDir.childDirectory('a_plugin')); | 
|  | package.getExamples().first.readmeFile.writeAsStringSync(''' | 
|  | # a_plugin_ios_example | 
|  |  | 
|  | Some random description. | 
|  | '''); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['readme-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder(<Matcher>[ | 
|  | contains('The example README for a platform implementation package ' | 
|  | 'should warn readers about its intended use. Please copy the ' | 
|  | 'example README from another implementation package in this ' | 
|  | 'repository.'), | 
|  | contains('Missing implementation package example warning'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('passes for a plugin implementation package with the expected content', | 
|  | () async { | 
|  | final RepositoryPackage package = createFakePlugin( | 
|  | 'a_plugin', | 
|  | packagesDir.childDirectory('a_plugin'), | 
|  | platformSupport: <String, PlatformDetails>{ | 
|  | platformAndroid: const PlatformDetails(PlatformSupport.inline), | 
|  | }, | 
|  | ); | 
|  | // Write a README with an OS support table so that the main README check | 
|  | // passes. | 
|  | package.readmeFile.writeAsStringSync(''' | 
|  | # a_plugin | 
|  |  | 
|  | |                | Android | | 
|  | |----------------|---------| | 
|  | | **Support**    | SDK 19+ | | 
|  |  | 
|  | A great plugin. | 
|  | '''); | 
|  | package.getExamples().first.readmeFile.writeAsStringSync(''' | 
|  | # Platform Implementation Test App | 
|  |  | 
|  | This is a test app for manual testing and automated integration testing | 
|  | of this platform implementation. It is not intended to demonstrate actual use of | 
|  | this package, since the intent is that plugin clients use the app-facing | 
|  | package. | 
|  |  | 
|  | Unless you are making changes to this implementation package, this example is | 
|  | very unlikely to be relevant. | 
|  | '''); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['readme-check']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAll(<Matcher>[ | 
|  | contains('  Checking README.md...'), | 
|  | contains('  Checking example/README.md...'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test( | 
|  | 'fails when multi-example top-level example directory README still has ' | 
|  | 'application template boilerplate', () async { | 
|  | final RepositoryPackage package = createFakePackage( | 
|  | 'a_package', packagesDir, | 
|  | examples: <String>['example1', 'example2']); | 
|  | package.directory | 
|  | .childDirectory('example') | 
|  | .childFile('README.md') | 
|  | .writeAsStringSync(''' | 
|  | ## Getting Started | 
|  |  | 
|  | This project is a starting point for a Flutter application. | 
|  |  | 
|  | A few resources to get you started if this is your first Flutter project: | 
|  |  | 
|  | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) | 
|  | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) | 
|  |  | 
|  | For help getting started with Flutter development, view the | 
|  | [online documentation](https://docs.flutter.dev/), which offers tutorials, | 
|  | samples, guidance on mobile development, and a full API reference. | 
|  | '''); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['readme-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder(<Matcher>[ | 
|  | contains('The boilerplate section about getting started with Flutter ' | 
|  | 'should not be left in.'), | 
|  | contains('Contains template boilerplate'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | group('plugin OS support', () { | 
|  | test( | 
|  | 'does not check support table for anything other than app-facing plugin packages', | 
|  | () async { | 
|  | const String federatedPluginName = 'a_federated_plugin'; | 
|  | final Directory federatedDir = | 
|  | packagesDir.childDirectory(federatedPluginName); | 
|  | // A non-plugin package. | 
|  | createFakePackage('a_package', packagesDir); | 
|  | // Non-app-facing parts of a federated plugin. | 
|  | createFakePlugin( | 
|  | '${federatedPluginName}_platform_interface', federatedDir); | 
|  | createFakePlugin('${federatedPluginName}_android', federatedDir); | 
|  |  | 
|  | final List<String> output = await runCapturingPrint(runner, <String>[ | 
|  | 'readme-check', | 
|  | ]); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAll(<Matcher>[ | 
|  | contains('Running for a_package...'), | 
|  | contains('Running for a_federated_plugin_platform_interface...'), | 
|  | contains('Running for a_federated_plugin_android...'), | 
|  | contains('No issues found!'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('fails when non-federated plugin is missing an OS support table', | 
|  | () async { | 
|  | createFakePlugin('a_plugin', packagesDir); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['readme-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder(<Matcher>[ | 
|  | contains('No OS support table found'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test( | 
|  | 'fails when app-facing part of a federated plugin is missing an OS support table', | 
|  | () async { | 
|  | createFakePlugin('a_plugin', packagesDir.childDirectory('a_plugin')); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['readme-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder(<Matcher>[ | 
|  | contains('No OS support table found'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('fails the OS support table is missing the header', () async { | 
|  | final RepositoryPackage plugin = | 
|  | createFakePlugin('a_plugin', packagesDir); | 
|  |  | 
|  | plugin.readmeFile.writeAsStringSync(''' | 
|  | A very useful plugin. | 
|  |  | 
|  | | **Support**    | SDK 21+ | iOS 10+* | [See `camera_web `][1] | | 
|  | '''); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['readme-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder(<Matcher>[ | 
|  | contains('OS support table does not have the expected header format'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('fails if the OS support table is missing a supported OS', () async { | 
|  | final RepositoryPackage plugin = createFakePlugin( | 
|  | 'a_plugin', | 
|  | packagesDir, | 
|  | platformSupport: <String, PlatformDetails>{ | 
|  | platformAndroid: const PlatformDetails(PlatformSupport.inline), | 
|  | platformIOS: const PlatformDetails(PlatformSupport.inline), | 
|  | platformWeb: const PlatformDetails(PlatformSupport.inline), | 
|  | }, | 
|  | ); | 
|  |  | 
|  | plugin.readmeFile.writeAsStringSync(''' | 
|  | A very useful plugin. | 
|  |  | 
|  | |                | Android | iOS      | | 
|  | |----------------|---------|----------| | 
|  | | **Support**    | SDK 21+ | iOS 10+* | | 
|  | '''); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['readme-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder(<Matcher>[ | 
|  | contains('  OS support table does not match supported platforms:\n' | 
|  | '    Actual:     android, ios, web\n' | 
|  | '    Documented: android, ios'), | 
|  | contains('Incorrect OS support table'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('fails if the OS support table lists an extra OS', () async { | 
|  | final RepositoryPackage plugin = createFakePlugin( | 
|  | 'a_plugin', | 
|  | packagesDir, | 
|  | platformSupport: <String, PlatformDetails>{ | 
|  | platformAndroid: const PlatformDetails(PlatformSupport.inline), | 
|  | platformIOS: const PlatformDetails(PlatformSupport.inline), | 
|  | }, | 
|  | ); | 
|  |  | 
|  | plugin.readmeFile.writeAsStringSync(''' | 
|  | A very useful plugin. | 
|  |  | 
|  | |                | Android | iOS      | Web                    | | 
|  | |----------------|---------|----------|------------------------| | 
|  | | **Support**    | SDK 21+ | iOS 10+* | [See `camera_web `][1] | | 
|  | '''); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['readme-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder(<Matcher>[ | 
|  | contains('  OS support table does not match supported platforms:\n' | 
|  | '    Actual:     android, ios\n' | 
|  | '    Documented: android, ios, web'), | 
|  | contains('Incorrect OS support table'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('fails if the OS support table has unexpected OS formatting', | 
|  | () async { | 
|  | final RepositoryPackage plugin = createFakePlugin( | 
|  | 'a_plugin', | 
|  | packagesDir, | 
|  | platformSupport: <String, PlatformDetails>{ | 
|  | platformAndroid: const PlatformDetails(PlatformSupport.inline), | 
|  | platformIOS: const PlatformDetails(PlatformSupport.inline), | 
|  | platformMacOS: const PlatformDetails(PlatformSupport.inline), | 
|  | platformWeb: const PlatformDetails(PlatformSupport.inline), | 
|  | }, | 
|  | ); | 
|  |  | 
|  | plugin.readmeFile.writeAsStringSync(''' | 
|  | A very useful plugin. | 
|  |  | 
|  | |                | android | ios      | MacOS | web                    | | 
|  | |----------------|---------|----------|-------|------------------------| | 
|  | | **Support**    | SDK 21+ | iOS 10+* | 10.11 | [See `camera_web `][1] | | 
|  | '''); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['readme-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder(<Matcher>[ | 
|  | contains('  Incorrect OS capitalization: android, ios, MacOS, web\n' | 
|  | '    Please use standard capitalizations: Android, iOS, macOS, Web\n'), | 
|  | contains('Incorrect OS support formatting'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | group('code blocks', () { | 
|  | test('fails on missing info string', () async { | 
|  | final RepositoryPackage package = | 
|  | createFakePackage('a_package', packagesDir); | 
|  |  | 
|  | package.readmeFile.writeAsStringSync(''' | 
|  | Example: | 
|  |  | 
|  | ``` | 
|  | void main() { | 
|  | // ... | 
|  | } | 
|  | ``` | 
|  | '''); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['readme-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder(<Matcher>[ | 
|  | contains('Code block at line 3 is missing a language identifier.'), | 
|  | contains('Missing language identifier for code block'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('allows unknown info strings', () async { | 
|  | final RepositoryPackage package = | 
|  | createFakePackage('a_package', packagesDir); | 
|  |  | 
|  | package.readmeFile.writeAsStringSync(''' | 
|  | Example: | 
|  |  | 
|  | ```someunknowninfotag | 
|  | A B C | 
|  | ``` | 
|  | '''); | 
|  |  | 
|  | final List<String> output = await runCapturingPrint(runner, <String>[ | 
|  | 'readme-check', | 
|  | ]); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAll(<Matcher>[ | 
|  | contains('Running for a_package...'), | 
|  | contains('No issues found!'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('allows space around info strings', () async { | 
|  | final RepositoryPackage package = | 
|  | createFakePackage('a_package', packagesDir); | 
|  |  | 
|  | package.readmeFile.writeAsStringSync(''' | 
|  | Example: | 
|  |  | 
|  | ```  dart | 
|  | A B C | 
|  | ``` | 
|  | '''); | 
|  |  | 
|  | final List<String> output = await runCapturingPrint(runner, <String>[ | 
|  | 'readme-check', | 
|  | ]); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAll(<Matcher>[ | 
|  | contains('Running for a_package...'), | 
|  | contains('No issues found!'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('passes when excerpt requirement is met', () async { | 
|  | final RepositoryPackage package = createFakePackage( | 
|  | 'a_package', | 
|  | packagesDir, | 
|  | ); | 
|  |  | 
|  | package.readmeFile.writeAsStringSync(''' | 
|  | Example: | 
|  |  | 
|  | <?code-excerpt "main.dart (SomeSection)"?> | 
|  | ```dart | 
|  | A B C | 
|  | ``` | 
|  | '''); | 
|  |  | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['readme-check', '--require-excerpts']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAll(<Matcher>[ | 
|  | contains('Running for a_package...'), | 
|  | contains('No issues found!'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('fails on missing excerpt tag when requested', () async { | 
|  | final RepositoryPackage package = | 
|  | createFakePackage('a_package', packagesDir); | 
|  |  | 
|  | package.readmeFile.writeAsStringSync(''' | 
|  | Example: | 
|  |  | 
|  | ```dart | 
|  | A B C | 
|  | ``` | 
|  | '''); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['readme-check', '--require-excerpts'], | 
|  | errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder(<Matcher>[ | 
|  | contains('Dart code block at line 3 is not managed by code-excerpt.'), | 
|  | // Ensure that the failure message links to instructions. | 
|  | contains( | 
|  | 'https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md'), | 
|  | contains('Missing code-excerpt management for code block'), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  | }); | 
|  | } |