mirror of
https://github.com/flutter/packages.git
synced 2025-05-22 19:26:43 +08:00

Contributes to https://github.com/flutter/website/issues/10363 by updating to the latest links and updating some links to use new `/to/` links that can be updated as needed.
707 lines
21 KiB
Dart
707 lines
21 KiB
Dart
// 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:file/memory.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:test/test.dart';
|
|
|
|
import 'mocks.dart';
|
|
import 'util.dart';
|
|
|
|
void main() {
|
|
late CommandRunner<void> runner;
|
|
late RecordingProcessRunner processRunner;
|
|
late FileSystem fileSystem;
|
|
late MockPlatform mockPlatform;
|
|
late Directory packagesDir;
|
|
|
|
setUp(() {
|
|
fileSystem = MemoryFileSystem();
|
|
mockPlatform = MockPlatform();
|
|
packagesDir = fileSystem.currentDirectory.childDirectory('packages');
|
|
createPackagesDirectory(parentDir: packagesDir.parent);
|
|
processRunner = RecordingProcessRunner();
|
|
final ReadmeCheckCommand command = ReadmeCheckCommand(
|
|
packagesDir,
|
|
processRunner: processRunner,
|
|
platform: mockPlatform,
|
|
);
|
|
|
|
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'),
|
|
]),
|
|
);
|
|
});
|
|
});
|
|
}
|