Skip to content

Commit

Permalink
Rewrite the 'exported extension method' tests; add basic extension te…
Browse files Browse the repository at this point in the history
…sts. (#3738)

I found this file and it was very hard for me to understand. It contained three
tests for extensions being exported from private to public API. I've preserved
the tests, but in an entirely new format. It is more standard, I think, and
more readable.

Since these were the only tests in a file called 'extension_methods_test', I
decided that I should add a few basic extension tests (and move the file to
extension_tests) to differentiate, a bit, what the file should contain.
  • Loading branch information
srawlins authored Apr 3, 2024
1 parent b45174e commit 2559a77
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 172 deletions.
52 changes: 2 additions & 50 deletions test/dartdoc_test_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ analyzer:
packagePath, libraryName, Uri.file('$packagePath/'));
}

Future<PackageGraph> _bootPackageFromFiles(Iterable<d.Descriptor> files,
Future<PackageGraph> bootPackageFromFiles(Iterable<d.Descriptor> files,
{List<String> additionalArguments = const []}) async {
var packagePathBasename =
resourceProvider.pathContext.basename(packagePath);
Expand All @@ -103,7 +103,7 @@ analyzer:
{String libraryPreamble = '',
Iterable<d.Descriptor> extraFiles = const [],
List<String> additionalArguments = const []}) async {
return (await _bootPackageFromFiles([
return (await bootPackageFromFiles([
d.dir('lib', [
d.file('lib.dart', '''
$libraryPreamble
Expand All @@ -118,54 +118,6 @@ $libraryContent
.named(libraryName);
}

/// Similar to [bootPackageWithLibrary], but allows for more complex
/// cases to test the edges of canonicalization.
///
/// - Puts [reexportedContent] in a library named [libraryName]_src in
/// `lib/src` (if [reexportPrivate] is true), or 'lib/subdir'.
/// - Creates a reexporting library named [libraryName]_lib in `lib` that
/// reexports [libraryName]_src.
/// - Creates [libraryName] containing [libraryContent] that can optionally
/// import 'lib.dart' to import the reexporting library.
///
/// Optionally, specify [show] or [hide] to change whether the reexport
/// gives access to the full namespace.
Future<Library> bootPackageWithReexportedLibrary(
String reexportedContent, String libraryContent,
{bool reexportPrivate = false,
List<String> show = const [],
List<String> hide = const []}) async {
final subdir = reexportPrivate ? 'src' : 'subdir';
if (show.isNotEmpty && hide.isNotEmpty) {
throw DartdocTestBaseFailure('Can not specify show and hide');
}
final showHideString = '${show.isNotEmpty ? 'show ${show.join(', ')}' : ''}'
'${hide.isNotEmpty ? 'hide ${hide.join(', ')}' : ''}';

return (await _bootPackageFromFiles([
d.dir('lib', [
d.dir(subdir, [
d.file('lib.dart', '''
library ${libraryName}_src;
$reexportedContent
'''),
]),
d.file('lib.dart', '''
library ${libraryName}_lib;
export '$subdir/lib.dart' $showHideString;
'''),
d.file('importing_lib.dart', '''
library $libraryName;
$libraryContent
'''),
])
]))
.libraries
.named(libraryName);
}

Future<Dartdoc> buildDartdoc({
List<String> excludeLibraries = const [],
List<String> additionalArguments = const [],
Expand Down
116 changes: 0 additions & 116 deletions test/extension_methods_test.dart

This file was deleted.

133 changes: 133 additions & 0 deletions test/extensions_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) 2024, 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:dartdoc/src/markdown_processor.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import 'dartdoc_test_base.dart';
import 'src/test_descriptor_utils.dart' as d;
import 'src/utils.dart';

void main() {
defineReflectiveSuite(() {
defineReflectiveTests(ExtensionMethodsTest);
defineReflectiveTests(ExtensionMethodsExportTest);
});
}

@reflectiveTest
class ExtensionMethodsTest extends DartdocTestBase {
@override
String get libraryName => 'extension_methods';

void test_referenceToExtension() async {
var library = await bootPackageWithLibrary('''
extension Ex on int {}
/// Text [Ex].
var f() {}
''');

expect(
library.functions.named('f').documentationAsHtml,
contains('<a href="$linkPrefix/Ex.html">Ex</a>'),
);
}

void test_referenceToExtensionMethod() async {
var library = await bootPackageWithLibrary('''
extension Ex on int {
void m() {}
}
/// Text [Ex.m].
var f() {}
''');

expect(
library.functions.named('f').documentationAsHtml,
contains('<a href="$linkPrefix/Ex/m.html">Ex.m</a>'),
);
}

// TODO(srawlins): Test everything else about extensions.
}

@reflectiveTest
class ExtensionMethodsExportTest extends DartdocTestBase {
@override
String get libraryName => 'extension_methods';

late Package package;

/// Verifies that comment reference text, [referenceText] attached to the
/// function, `f`, is resolved to [expected], and links to [href].
void expectReferenceValidFromF(
String referenceText, ModelElement expected, String href) {
var fFunction = package.functions.named('f');
var reference = getMatchingLinkElement(referenceText, fFunction)
.commentReferable as ModelElement;
expect(identical(reference.canonicalModelElement, expected), isTrue);
expect(expected.isCanonical, isTrue);
expect(expected.href, endsWith(href));
}

/// Sets up a package with three files:
///
/// * a private file containing a class, `C`, an extension on that class, `E`,
/// `E`, and a method in that extension, `m`.
/// * A public file that exports some members of the private file. The content
/// of this file is specified with [exportingLibraryContent].
/// * Another public file which is completely unrelated to the first two (no
/// imports or exports linking them), which contains a function, `f`.
Future<void> setupWith(String exportingLibraryContent) async {
var packageGraph = await bootPackageFromFiles([
d.dir('lib', [
d.dir('src', [
d.file('lib.dart', '''
class C {}
extension Ex on C {
void m() {}
}
'''),
]),
d.file('one.dart', exportingLibraryContent),
d.file('two.dart', '''
/// Comment.
var f() {}
'''),
])
]);
package = packageGraph.defaultPackage;
}

void test_reexportWithShow() async {
await setupWith("export 'src/lib.dart' show C, Ex;");

var ex = package.extensions.named('Ex');
var m = ex.instanceMethods.named('m');
expectReferenceValidFromF('Ex', ex, '%one/Ex.html');
expectReferenceValidFromF('Ex.m', m, '%one/Ex/m.html');
}

void test_reexportWithHide() async {
await setupWith("export 'src/lib.dart' hide A;");

var ex = package.extensions.named('Ex');
var m = ex.instanceMethods.named('m');
expectReferenceValidFromF('Ex', ex, '%one/Ex.html');
expectReferenceValidFromF('Ex.m', m, '%one/Ex/m.html');
}

void test_reexportFull() async {
await setupWith("export 'src/lib.dart';");

var ex = package.extensions.named('Ex');
var m = ex.instanceMethods.named('m');
expectReferenceValidFromF('Ex', ex, '%one/Ex.html');
expectReferenceValidFromF('Ex.m', m, '%one/Ex/m.html');
}
}
19 changes: 13 additions & 6 deletions test/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ import 'package:dartdoc/src/generator/resource_loader.dart';
import 'package:dartdoc/src/logging.dart';
import 'package:dartdoc/src/markdown_processor.dart';
import 'package:dartdoc/src/matching_link_result.dart';
import 'package:dartdoc/src/model/model_element.dart';
import 'package:dartdoc/src/model/package_builder.dart';
import 'package:dartdoc/src/model/package_graph.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/package_config_provider.dart';
import 'package:dartdoc/src/package_meta.dart';
import 'package:dartdoc/src/warnings.dart';
Expand Down Expand Up @@ -356,12 +354,11 @@ bool get classModifiersAllowed =>
VersionRange(min: Version.parse('3.0.0-0.0-dev'), includeMin: true)
.allows(platformVersion);

extension ModelElementIterableExtensions<T extends ModelElement>
on Iterable<T> {
extension ModelElementIterableExtension<T extends ModelElement> on Iterable<T> {
T named(String name) => firstWhere((e) => e.name == name);
}

extension IterableStringExtensions on Iterable<String> {
extension IterableStringExtension on Iterable<String> {
/// The main content line of `this`.
Iterable<String> get mainContent =>
skipWhile((line) => !line.contains('"dartdoc-main-content"'))
Expand All @@ -377,6 +374,16 @@ extension IterableStringExtensions on Iterable<String> {
}
}

extension PackageExtension on Package {
/// Gathers all extensions found across a package.
Iterable<Extension> get extensions =>
libraries.expand((library) => library.extensions);

/// Gathers all functions found across a package.
Iterable<ModelFunction> get functions =>
libraries.expand((library) => library.functions);
}

/// Extension methods just for tests.
extension on ResourceProvider {
Future<void> writeDartdocResource(String resourcePath, String content) async {
Expand Down

0 comments on commit 2559a77

Please sign in to comment.