blob: a324047c4723d13904695e7b4540189deb3d75d4 [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// 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:nnbd_migration/src/front_end/migration_info.dart';
import 'package:nnbd_migration/src/front_end/path_mapper.dart';
import 'package:nnbd_migration/src/front_end/region_renderer.dart';
import 'package:nnbd_migration/src/front_end/web/edit_details.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'nnbd_migration_test_base.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(RegionRendererTest);
defineReflectiveTests(RegionRendererTestDriveD);
});
}
@reflectiveTest
class RegionRendererTest extends RegionRendererTestBase {
/// Returns the basename of [testFile], used in traces.
String get _testFileBasename =>
resourceProvider.pathContext.basename(testFile!);
Future<void> test_informationalRegion_containsTrace() async {
await buildInfoForSingleTestFile('f(int a) => a.isEven;',
migratedContent: 'f(int a) => a.isEven;');
var response = renderRegion(5);
expect(response.traces, hasLength(1));
var trace = response.traces![0];
expect(trace.description, equals('Non-nullability reason'));
}
Future<void> test_informationalRegion_containsTraceEntryDescriptions() async {
await buildInfoForSingleTestFile('f(int a) => a.isEven;',
migratedContent: 'f(int a) => a.isEven;');
var response = renderRegion(5);
expect(response.traces, hasLength(1));
var trace = response.traces![0];
expect(trace.entries, hasLength(2));
expect(trace.entries[0].description,
equals('parameter 0 of f ($_testFileBasename:1:3)'));
expect(trace.entries[1].description, equals('data flow'));
}
Future<void> test_informationalRegion_containsTraceLinks() async {
await buildInfoForSingleTestFile('f(int a) => a.isEven;',
migratedContent: 'f(int a) => a.isEven;');
var response = renderRegion(5);
expect(response.traces, hasLength(1));
var trace = response.traces![0];
var entry = trace.entries[0];
expect(entry.link, isNotNull);
var testFileUriPath = resourceProvider.pathContext.toUri(testFile!).path;
expect(entry.link!.href,
equals('$testFileUriPath?offset=2&line=1&authToken=AUTH_TOKEN'));
expect(entry.link!.path,
equals(resourceProvider.pathContext.toUri(_testFileBasename).path));
}
Future<void> test_modifiedOutput_containsExplanation() async {
await buildInfoForSingleTestFile('int a = null;',
migratedContent: 'int? a = null;');
var response = renderRegion(3);
expect(response.explanation, equals("Changed type 'int' to be nullable"));
}
Future<void> test_modifiedOutput_containsPath() async {
await buildInfoForSingleTestFile('int a = null;',
migratedContent: 'int? a = null;');
var response = renderRegion(3);
expect(response.displayPath, equals(testFile));
expect(response.uriPath, equals(pathMapper!.map(testFile!)));
expect(response.line, equals(1));
}
Future<void> test_modifiedOutput_containsTraceForNullabilityReason() async {
await buildInfoForSingleTestFile('int a = null;',
migratedContent: 'int? a = null;');
var response = renderRegion(3);
expect(response.traces, hasLength(1));
var trace = response.traces![0];
expect(trace.description, equals('Nullability reason'));
expect(trace.entries, hasLength(4));
expect(trace.entries[0].description, equals('a ($_testFileBasename:1:1)'));
expect(trace.entries[1].description, equals('data flow'));
expect(trace.entries[2].description,
equals('null literal ($_testFileBasename:1:9)'));
expect(trace.entries[3].description, equals('literal expression'));
}
Future<void> test_unmodifiedOutput_containsExplanation() async {
await buildInfoForSingleTestFile('f(int a) => a.isEven;',
migratedContent: 'f(int a) => a.isEven;');
var response = renderRegion(5);
expect(response.explanation, equals("Type 'int' was not made nullable"));
}
Future<void> test_unmodifiedOutput_containsPath() async {
await buildInfoForSingleTestFile('f(int a) => a.isEven;',
migratedContent: 'f(int a) => a.isEven;');
var response = renderRegion(5);
expect(response.displayPath, equals(testFile));
expect(response.uriPath, equals(pathMapper!.map(testFile!)));
expect(response.line, equals(1));
}
}
class RegionRendererTestBase extends NnbdMigrationTestBase {
PathMapper? pathMapper;
/// Render the region at [offset], using a [MigrationInfo] which knows only
/// about the library at `infos.single`.
EditDetails renderRegion(int offset) {
var migrationInfo =
MigrationInfo(infos, {}, resourceProvider.pathContext, projectPath);
var unitInfo = infos!.single;
var region = unitInfo.regionAt(offset);
pathMapper = PathMapper(resourceProvider);
return RegionRenderer(
region, unitInfo, migrationInfo, pathMapper, 'AUTH_TOKEN')
.render();
}
}
@reflectiveTest
class RegionRendererTestDriveD extends RegionRendererTestBase {
@override
String get homePath => _switchToDriveD(super.homePath);
@override
void setUp() {
super.setUp();
}
Future<void>
test_informationalRegion_containsTraceLinks_separateDrive() async {
// See https://github.com/dart-lang/sdk/issues/43178. Linking from a file on
// one drive to a file on another drive can cause problems.
await buildInfoForSingleTestFile(r'''
f(List<int> a) {
if (1 == 2) List.from(a);
}
g() {
f(null);
}
''', migratedContent: r'''
f(List<int >? a) {
if (1 == 2) List.from(a!);
}
g() {
f(null);
}
''');
var response = renderRegion(44); // The inserted null-check.
expect(response.displayPath,
equals(_switchToDriveD(convertPath('/home/tests/bin/test.dart'))));
expect(response.traces, hasLength(2));
var trace = response.traces![1];
expect(trace.description, equals('Non-nullability reason'));
expect(trace.entries, hasLength(1));
var entry = trace.entries[0];
expect(entry.link, isNotNull);
var sdkCoreLib = convertPath('/sdk/lib/core/core.dart');
var sdkCoreLibUriPath = resourceProvider.pathContext.toUri(sdkCoreLib).path;
var coreLibText = resourceProvider.getFile(sdkCoreLib).readAsStringSync();
var expectedOffset =
'List.from'.allMatches(coreLibText).single.start + 'List.'.length;
var expectedLine =
'\n'.allMatches(coreLibText.substring(0, expectedOffset)).length + 1;
expect(
entry.link!.href,
equals('$sdkCoreLibUriPath?'
'offset=$expectedOffset&'
'line=$expectedLine&'
'authToken=AUTH_TOKEN'));
// On Windows, the path will simply be the absolute path to the core
// library, because there is no relative route from C:\ to D:\. On Posix,
// the path is relative.
var expectedLinkPath = resourceProvider.pathContext.style == p.Style.windows
? sdkCoreLibUriPath
: '../../..$sdkCoreLibUriPath';
expect(entry.link!.path, equals(expectedLinkPath));
}
/// On Windows, replace the C:\ relative root in [path] with the D:\ relative
/// root.
///
/// On Posix, nothing is be replaced.
String _switchToDriveD(String path) {
assert(resourceProvider.pathContext.isAbsolute(path));
return resourceProvider
.convertPath(path)
.replaceFirst(RegExp('^C:\\\\'), 'D:\\');
}
}