[analysis_server] Add imports for test/group snippets
Fixes https://github.com/Dart-Code/Dart-Code/issues/5421
Change-Id: I70b1bac0e86117c19324d482ba993f950b5be931
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/411002
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/snippets/dart/test_definition.dart b/pkg/analysis_server/lib/src/services/snippets/dart/test_definition.dart
index 9d702ff..c0721f8 100644
--- a/pkg/analysis_server/lib/src/services/snippets/dart/test_definition.dart
+++ b/pkg/analysis_server/lib/src/services/snippets/dart/test_definition.dart
@@ -4,10 +4,13 @@
import 'package:analysis_server/src/services/snippets/snippet.dart';
import 'package:analysis_server/src/services/snippets/snippet_producer.dart';
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/src/dart/analysis/session_helper.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
/// Produces a [Snippet] that creates a `test()` block.
-class TestDefinition extends DartSnippetProducer {
+class TestDefinition extends DartSnippetProducer with TestSnippetMixin {
static const prefix = 'test';
static const label = 'test';
@@ -21,7 +24,8 @@
var builder = ChangeBuilder(session: request.analysisSession);
var indent = utils.getLinePrefix(request.offset);
- await builder.addDartFileEdit(request.filePath, (builder) {
+ await builder.addDartFileEdit(request.filePath, (builder) async {
+ await addRequiredImports(builder);
builder.addReplacement(request.replacementRange, (builder) {
void writeIndented(String string) => builder.write('$indent$string');
builder.write("test('");
@@ -46,3 +50,35 @@
return isInTestDirectory;
}
}
+
+mixin TestSnippetMixin {
+ final _flutterTestUri = Uri.parse('package:flutter_test/flutter_test.dart');
+
+ final _dartTestUri = Uri.parse('package:test/test.dart');
+
+ AnalysisSessionHelper get sessionHelper;
+
+ /// Adds imports for `test`/`group` if required.
+ ///
+ /// Both 'package:test' and 'package:flutter_test' are checked because Flutter
+ /// projects can use either and we don't want to add the other.
+ Future<void> addRequiredImports(DartFileEditBuilder builder) async {
+ if (builder.importsLibrary(_dartTestUri) ||
+ builder.importsLibrary(_flutterTestUri)) {
+ return;
+ }
+
+ var testUri = await getTestLibraryUri();
+ builder.importLibrary(testUri);
+ }
+
+ /// Gets the URI for the test library to import depending on whether
+ /// flutter_test is available or not.
+ Future<Uri> getTestLibraryUri() async {
+ var flutterTest = await sessionHelper.session.getLibraryByUri(
+ _flutterTestUri.toString(),
+ );
+
+ return flutterTest is LibraryElementResult ? _flutterTestUri : _dartTestUri;
+ }
+}
diff --git a/pkg/analysis_server/lib/src/services/snippets/dart/test_group_definition.dart b/pkg/analysis_server/lib/src/services/snippets/dart/test_group_definition.dart
index 16e29e2..3bf471a 100644
--- a/pkg/analysis_server/lib/src/services/snippets/dart/test_group_definition.dart
+++ b/pkg/analysis_server/lib/src/services/snippets/dart/test_group_definition.dart
@@ -2,12 +2,13 @@
// for details. 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:analysis_server/src/services/snippets/dart/test_definition.dart';
import 'package:analysis_server/src/services/snippets/snippet.dart';
import 'package:analysis_server/src/services/snippets/snippet_producer.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
/// Produces a [Snippet] that creates a `group()` block.
-class TestGroupDefinition extends DartSnippetProducer {
+class TestGroupDefinition extends DartSnippetProducer with TestSnippetMixin {
static const prefix = 'group';
static const label = 'group';
@@ -21,7 +22,8 @@
var builder = ChangeBuilder(session: request.analysisSession);
var indent = utils.getLinePrefix(request.offset);
- await builder.addDartFileEdit(request.filePath, (builder) {
+ await builder.addDartFileEdit(request.filePath, (builder) async {
+ await addRequiredImports(builder);
builder.addReplacement(request.replacementRange, (builder) {
void writeIndented(String string) => builder.write('$indent$string');
builder.write("group('");
diff --git a/pkg/analysis_server/test/lsp/completion_dart_test.dart b/pkg/analysis_server/test/lsp/completion_dart_test.dart
index 2c54c89..a9ab5d7 100644
--- a/pkg/analysis_server/test/lsp/completion_dart_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_dart_test.dart
@@ -4596,6 +4596,8 @@
);
expect(updated, r'''
+import 'package:test/test.dart';
+
void f() {
test('${1:test name}', () {
$0
@@ -4620,6 +4622,8 @@
);
expect(updated, r'''
+import 'package:test/test.dart';
+
void f() {
group('${1:group name}', () {
$0
diff --git a/pkg/analysis_server/test/services/snippets/dart/test_definition_test.dart b/pkg/analysis_server/test/services/snippets/dart/test_definition_test.dart
index 07e1668..fc24473 100644
--- a/pkg/analysis_server/test/services/snippets/dart/test_definition_test.dart
+++ b/pkg/analysis_server/test/services/snippets/dart/test_definition_test.dart
@@ -2,7 +2,6 @@
// for details. 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:analysis_server/src/protocol_server.dart';
import 'package:analysis_server/src/services/snippets/dart/test_definition.dart';
import 'package:analyzer/src/test_utilities/test_code_format.dart';
import 'package:test/test.dart';
@@ -27,6 +26,117 @@
@override
String get prefix => TestDefinition.prefix;
+ Future<void> test_import_dart() async {
+ testFilePath = convertPath('$testPackageLibPath/test/foo_test.dart');
+ var code = TestCode.parse(r'''
+void f() {
+ test^
+}
+''');
+ var snippet = await expectValidSnippet(code);
+ var result = applySnippet(code, snippet);
+ expect(result, '''
+import 'package:test/test.dart';
+
+void f() {
+ test('test name', () {
+
+ });
+}
+''');
+ }
+
+ Future<void> test_import_dart_existing() async {
+ testFilePath = convertPath('$testPackageLibPath/test/foo_test.dart');
+ var code = TestCode.parse(r'''
+import 'package:test/test.dart';
+
+void f() {
+ test^
+}
+''');
+ var snippet = await expectValidSnippet(code);
+ var result = applySnippet(code, snippet);
+ expect(result, '''
+import 'package:test/test.dart';
+
+void f() {
+ test('test name', () {
+
+ });
+}
+''');
+ }
+
+ Future<void> test_import_flutter() async {
+ writeTestPackageConfig(flutter_test: true);
+ testFilePath = convertPath('$testPackageLibPath/test/foo_test.dart');
+ var code = TestCode.parse(r'''
+void f() {
+ test^
+}
+''');
+ var snippet = await expectValidSnippet(code);
+ var result = applySnippet(code, snippet);
+ expect(result, '''
+import 'package:flutter_test/flutter_test.dart';
+
+void f() {
+ test('test name', () {
+
+ });
+}
+''');
+ }
+
+ Future<void> test_import_flutter_existing() async {
+ writeTestPackageConfig(flutter_test: true);
+ testFilePath = convertPath('$testPackageLibPath/test/foo_test.dart');
+ var code = TestCode.parse(r'''
+import 'package:flutter_test/flutter_test.dart';
+
+void f() {
+ test^
+}
+''');
+ var snippet = await expectValidSnippet(code);
+ var result = applySnippet(code, snippet);
+ expect(result, '''
+import 'package:flutter_test/flutter_test.dart';
+
+void f() {
+ test('test name', () {
+
+ });
+}
+''');
+ }
+
+ /// Ensure we don't import package:flutter_test if package:test is already
+ /// imported.
+ Future<void> test_import_flutter_existingDart() async {
+ writeTestPackageConfig(flutter_test: true);
+ testFilePath = convertPath('$testPackageLibPath/test/foo_test.dart');
+ var code = TestCode.parse(r'''
+import 'package:test/test.dart';
+
+void f() {
+ test^
+}
+''');
+ var snippet = await expectValidSnippet(code);
+ var result = applySnippet(code, snippet);
+ expect(result, '''
+import 'package:test/test.dart';
+
+void f() {
+ test('test name', () {
+
+ });
+}
+''');
+ }
+
Future<void> test_inTestFile() async {
testFilePath = convertPath('$testPackageLibPath/test/foo_test.dart');
var code = TestCode.parse(r'''
@@ -38,11 +148,10 @@
expect(snippet.prefix, prefix);
expect(snippet.label, label);
expect(snippet.change.edits, hasLength(1));
- var result = code.code;
- for (var edit in snippet.change.edits) {
- result = SourceEdit.applySequence(result, edit.edits);
- }
+ var result = applySnippet(code, snippet);
expect(result, '''
+import 'package:test/test.dart';
+
void f() {
test('test name', () {
@@ -50,11 +159,11 @@
}
''');
expect(snippet.change.selection!.file, testFile.path);
- expect(snippet.change.selection!.offset, 40);
+ expect(snippet.change.selection!.offset, 74);
expect(snippet.change.linkedEditGroups.map((group) => group.toJson()), [
{
'positions': [
- {'file': testFile.path, 'offset': 19},
+ {'file': testFile.path, 'offset': 53},
],
'length': 9,
'suggestions': [],
diff --git a/pkg/analysis_server/test/services/snippets/dart/test_group_definition_test.dart b/pkg/analysis_server/test/services/snippets/dart/test_group_definition_test.dart
index d9396d3..794c3bc 100644
--- a/pkg/analysis_server/test/services/snippets/dart/test_group_definition_test.dart
+++ b/pkg/analysis_server/test/services/snippets/dart/test_group_definition_test.dart
@@ -2,7 +2,6 @@
// for details. 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:analysis_server/src/protocol_server.dart';
import 'package:analysis_server/src/services/snippets/dart/test_group_definition.dart';
import 'package:analyzer/src/test_utilities/test_code_format.dart';
import 'package:test/test.dart';
@@ -27,6 +26,117 @@
@override
String get prefix => TestGroupDefinition.prefix;
+ Future<void> test_import_dart() async {
+ testFilePath = convertPath('$testPackageLibPath/test/foo_test.dart');
+ var code = TestCode.parse(r'''
+void f() {
+ group^
+}
+''');
+ var snippet = await expectValidSnippet(code);
+ var result = applySnippet(code, snippet);
+ expect(result, '''
+import 'package:test/test.dart';
+
+void f() {
+ group('group name', () {
+
+ });
+}
+''');
+ }
+
+ Future<void> test_import_dart_existing() async {
+ testFilePath = convertPath('$testPackageLibPath/test/foo_test.dart');
+ var code = TestCode.parse(r'''
+import 'package:test/test.dart';
+
+void f() {
+ group^
+}
+''');
+ var snippet = await expectValidSnippet(code);
+ var result = applySnippet(code, snippet);
+ expect(result, '''
+import 'package:test/test.dart';
+
+void f() {
+ group('group name', () {
+
+ });
+}
+''');
+ }
+
+ Future<void> test_import_flutter() async {
+ writeTestPackageConfig(flutter_test: true);
+ testFilePath = convertPath('$testPackageLibPath/test/foo_test.dart');
+ var code = TestCode.parse(r'''
+void f() {
+ group^
+}
+''');
+ var snippet = await expectValidSnippet(code);
+ var result = applySnippet(code, snippet);
+ expect(result, '''
+import 'package:flutter_test/flutter_test.dart';
+
+void f() {
+ group('group name', () {
+
+ });
+}
+''');
+ }
+
+ Future<void> test_import_flutter_existing() async {
+ writeTestPackageConfig(flutter_test: true);
+ testFilePath = convertPath('$testPackageLibPath/test/foo_test.dart');
+ var code = TestCode.parse(r'''
+import 'package:flutter_test/flutter_test.dart';
+
+void f() {
+ group^
+}
+''');
+ var snippet = await expectValidSnippet(code);
+ var result = applySnippet(code, snippet);
+ expect(result, '''
+import 'package:flutter_test/flutter_test.dart';
+
+void f() {
+ group('group name', () {
+
+ });
+}
+''');
+ }
+
+ /// Ensure we don't import package:flutter_test if package:test is already
+ /// imported.
+ Future<void> test_import_flutter_existingDart() async {
+ writeTestPackageConfig(flutter_test: true);
+ testFilePath = convertPath('$testPackageLibPath/test/foo_test.dart');
+ var code = TestCode.parse(r'''
+import 'package:test/test.dart';
+
+void f() {
+ group^
+}
+''');
+ var snippet = await expectValidSnippet(code);
+ var result = applySnippet(code, snippet);
+ expect(result, '''
+import 'package:test/test.dart';
+
+void f() {
+ group('group name', () {
+
+ });
+}
+''');
+ }
+
Future<void> test_inTestFile() async {
testFilePath = convertPath('$testPackageLibPath/test/foo_test.dart');
var code = TestCode.parse(r'''
@@ -38,11 +148,10 @@
expect(snippet.prefix, prefix);
expect(snippet.label, label);
expect(snippet.change.edits, hasLength(1));
- var result = code.code;
- for (var edit in snippet.change.edits) {
- result = SourceEdit.applySequence(result, edit.edits);
- }
+ var result = applySnippet(code, snippet);
expect(result, '''
+import 'package:test/test.dart';
+
void f() {
group('group name', () {
@@ -50,11 +159,11 @@
}
''');
expect(snippet.change.selection!.file, testFile.path);
- expect(snippet.change.selection!.offset, 42);
+ expect(snippet.change.selection!.offset, 76);
expect(snippet.change.linkedEditGroups.map((group) => group.toJson()), [
{
'positions': [
- {'file': testFile.path, 'offset': 20},
+ {'file': testFile.path, 'offset': 54},
],
'length': 10,
'suggestions': [],
diff --git a/pkg/analysis_server/test/services/snippets/dart/test_support.dart b/pkg/analysis_server/test/services/snippets/dart/test_support.dart
index a147507..165262a 100644
--- a/pkg/analysis_server/test/services/snippets/dart/test_support.dart
+++ b/pkg/analysis_server/test/services/snippets/dart/test_support.dart
@@ -24,6 +24,14 @@
@override
bool get verifyNoTestUnitErrors => false;
+ String applySnippet(TestCode code, Snippet snippet) {
+ var result = code.code;
+ for (var edit in snippet.change.edits) {
+ result = SourceEdit.applySequence(result, edit.edits);
+ }
+ return result;
+ }
+
Future<void> assertSnippet(String content, String expected) async {
var code = TestCode.parse(content);
var expectedCode = TestCode.parse(expected);
diff --git a/pkg/analysis_server/test/support/configuration_files.dart b/pkg/analysis_server/test/support/configuration_files.dart
index d974b34..5edb780 100644
--- a/pkg/analysis_server/test/support/configuration_files.dart
+++ b/pkg/analysis_server/test/support/configuration_files.dart
@@ -39,6 +39,7 @@
PackageConfigFileBuilder? config,
String? languageVersion,
bool flutter = false,
+ bool flutter_test = false,
bool meta = false,
bool pedantic = false,
bool vector_math = false,
@@ -75,6 +76,11 @@
}
}
+ if (flutter_test) {
+ var libFolder = addFlutterTest();
+ config.add(name: 'flutter_test', rootPath: libFolder.parent.path);
+ }
+
if (pedantic) {
var libFolder = addPedantic();
config.add(name: 'pedantic', rootPath: libFolder.parent.path);
@@ -117,6 +123,7 @@
PackageConfigFileBuilder? config,
String? languageVersion,
bool flutter = false,
+ bool flutter_test = false,
bool meta = false,
bool pedantic = false,
bool vector_math = false,
@@ -128,6 +135,7 @@
languageVersion: languageVersion,
packageName: 'test',
flutter: flutter,
+ flutter_test: flutter_test,
meta: meta,
pedantic: pedantic,
vector_math: vector_math,
diff --git a/pkg/analyzer_utilities/lib/test/mock_packages/mock_packages.dart b/pkg/analyzer_utilities/lib/test/mock_packages/mock_packages.dart
index b76403f..298d26e 100644
--- a/pkg/analyzer_utilities/lib/test/mock_packages/mock_packages.dart
+++ b/pkg/analyzer_utilities/lib/test/mock_packages/mock_packages.dart
@@ -114,6 +114,11 @@
return packageFolder.getChildAssumingFolder('lib');
}
+ Folder addFlutterTest() {
+ var packageFolder = _addFiles('flutter_test');
+ return packageFolder.getChildAssumingFolder('lib');
+ }
+
Folder addJs() {
var packageFolder = _addFiles('js');
return packageFolder.getChildAssumingFolder('lib');
diff --git a/pkg/analyzer_utilities/lib/test/mock_packages/package_content/flutter_test/lib/flutter_test.dart b/pkg/analyzer_utilities/lib/test/mock_packages/package_content/flutter_test/lib/flutter_test.dart
new file mode 100644
index 0000000..bac6458
--- /dev/null
+++ b/pkg/analyzer_utilities/lib/test/mock_packages/package_content/flutter_test/lib/flutter_test.dart
@@ -0,0 +1,14 @@
+// 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.
+
+void test(Object description, dynamic Function() body) {}
+
+void group(Object description, void Function() body) {}
+
+void main() {
+ // Because this file is called 'flutter_test.dart' and is inside the 'test'
+ // folder, it will be considered a test suite. To avoid it failing the bots
+ // with "Invoked Dart programs must have a 'main' function defined", provide
+ // an empty main function.
+}
diff --git a/pkg/analyzer_utilities/lib/test/mock_packages/package_content/flutter_test/pubspec.yaml b/pkg/analyzer_utilities/lib/test/mock_packages/package_content/flutter_test/pubspec.yaml
new file mode 100644
index 0000000..ee363fb
--- /dev/null
+++ b/pkg/analyzer_utilities/lib/test/mock_packages/package_content/flutter_test/pubspec.yaml
@@ -0,0 +1 @@
+name: flutter_test