diff --git a/test/dartdoc_test_base.dart b/test/dartdoc_test_base.dart index 734413ae67..8b8482ad07 100644 --- a/test/dartdoc_test_base.dart +++ b/test/dartdoc_test_base.dart @@ -79,7 +79,7 @@ analyzer: packagePath, libraryName, Uri.file('$packagePath/')); } - Future _bootPackageFromFiles(Iterable files, + Future bootPackageFromFiles(Iterable files, {List additionalArguments = const []}) async { var packagePathBasename = resourceProvider.pathContext.basename(packagePath); @@ -103,7 +103,7 @@ analyzer: {String libraryPreamble = '', Iterable extraFiles = const [], List additionalArguments = const []}) async { - return (await _bootPackageFromFiles([ + return (await bootPackageFromFiles([ d.dir('lib', [ d.file('lib.dart', ''' $libraryPreamble @@ -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 bootPackageWithReexportedLibrary( - String reexportedContent, String libraryContent, - {bool reexportPrivate = false, - List show = const [], - List 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 buildDartdoc({ List excludeLibraries = const [], List additionalArguments = const [], diff --git a/test/extension_methods_test.dart b/test/extension_methods_test.dart deleted file mode 100644 index 3eedad92d6..0000000000 --- a/test/extension_methods_test.dart +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) 2023, 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/extension.dart'; -import 'package:dartdoc/src/model/method.dart'; -import 'package:dartdoc/src/model/model_element.dart'; -import 'package:dartdoc/src/model_utils.dart'; -import 'package:test/test.dart'; -import 'package:test_reflective_loader/test_reflective_loader.dart'; - -import 'dartdoc_test_base.dart'; -import 'src/utils.dart'; - -void main() { - defineReflectiveSuite(() { - if (classModifiersAllowed) { - defineReflectiveTests(ExtensionMethodsTest); - } - }); -} - -@reflectiveTest -class ExtensionMethodsTest extends DartdocTestBase { - @override - String get libraryName => 'extension_methods'; - - static const String reexportedContent = ''' -class AClassNotNeedingExtending {} - -class AClassNeedingExtending {} - -extension AnExtension on AClassNeedingExtending { - void aMethod() {} -} -'''; - - static const String libraryContent = ''' -/// This is an amazing public function. -var aPublicFunction() {} -'''; - - void expectReferenceValid( - ModelElement reference, ModelElement expected, String href) { - expect(identical(reference.canonicalModelElement, expected), isTrue); - expect(expected.isCanonical, isTrue); - expect(expected.href, endsWith(href)); - } - - void test_reexportWithShow() async { - var library = await bootPackageWithReexportedLibrary( - reexportedContent, libraryContent, - reexportPrivate: true, show: ['AClassNeedingExtending', 'AnExtension']); - var aPublicFunction = library.functions.named('aPublicFunction'); - var anExtension = library.package.libraries.wherePublic - .named('${libraryName}_lib') - .extensions - .named('AnExtension'); - var anExtensionMethod = anExtension.instanceMethods.named('aMethod'); - var anExtensionReference = - getMatchingLinkElement('AnExtension', aPublicFunction).commentReferable - as Extension; - expectReferenceValid(anExtensionReference, anExtension, - '%extension_methods_lib/AnExtension.html'); - var anExtensionMethodReference = - getMatchingLinkElement('AnExtension.aMethod', aPublicFunction) - .commentReferable as Method; - expectReferenceValid(anExtensionMethodReference, anExtensionMethod, - '%extension_methods_lib/AnExtension/aMethod.html'); - } - - void test_reexportWithHide() async { - var library = await bootPackageWithReexportedLibrary( - reexportedContent, libraryContent, - reexportPrivate: true, hide: ['AClassNotNeedingExtending']); - var aPublicFunction = library.functions.named('aPublicFunction'); - var anExtension = library.package.libraries.wherePublic - .named('${libraryName}_lib') - .extensions - .named('AnExtension'); - var anExtensionMethod = anExtension.instanceMethods.named('aMethod'); - var anExtensionReference = - getMatchingLinkElement('AnExtension', aPublicFunction).commentReferable - as Extension; - expectReferenceValid(anExtensionReference, anExtension, - '%extension_methods_lib/AnExtension.html'); - var anExtensionMethodReference = - getMatchingLinkElement('AnExtension.aMethod', aPublicFunction) - .commentReferable as Method; - expectReferenceValid(anExtensionMethodReference, anExtensionMethod, - '%extension_methods_lib/AnExtension/aMethod.html'); - } - - void test_reexportFull() async { - var library = await bootPackageWithReexportedLibrary( - reexportedContent, libraryContent, - reexportPrivate: true); - var aPublicFunction = library.functions.named('aPublicFunction'); - var anExtension = library.package.libraries.wherePublic - .named('${libraryName}_lib') - .extensions - .named('AnExtension'); - var anExtensionMethod = anExtension.instanceMethods.named('aMethod'); - var anExtensionReference = - getMatchingLinkElement('AnExtension', aPublicFunction).commentReferable - as Extension; - expectReferenceValid(anExtensionReference, anExtension, - '%extension_methods_lib/AnExtension.html'); - var anExtensionMethodReference = - getMatchingLinkElement('AnExtension.aMethod', aPublicFunction) - .commentReferable as Method; - expectReferenceValid(anExtensionMethodReference, anExtensionMethod, - '%extension_methods_lib/AnExtension/aMethod.html'); - } -} diff --git a/test/extensions_test.dart b/test/extensions_test.dart new file mode 100644 index 0000000000..63a432acb6 --- /dev/null +++ b/test/extensions_test.dart @@ -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('Ex'), + ); + } + + 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('Ex.m'), + ); + } + + // 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 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'); + } +} diff --git a/test/src/utils.dart b/test/src/utils.dart index 1a6482b50b..5213c6f10b 100644 --- a/test/src/utils.dart +++ b/test/src/utils.dart @@ -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'; @@ -356,12 +354,11 @@ bool get classModifiersAllowed => VersionRange(min: Version.parse('3.0.0-0.0-dev'), includeMin: true) .allows(platformVersion); -extension ModelElementIterableExtensions - on Iterable { +extension ModelElementIterableExtension on Iterable { T named(String name) => firstWhere((e) => e.name == name); } -extension IterableStringExtensions on Iterable { +extension IterableStringExtension on Iterable { /// The main content line of `this`. Iterable get mainContent => skipWhile((line) => !line.contains('"dartdoc-main-content"')) @@ -377,6 +374,16 @@ extension IterableStringExtensions on Iterable { } } +extension PackageExtension on Package { + /// Gathers all extensions found across a package. + Iterable get extensions => + libraries.expand((library) => library.extensions); + + /// Gathers all functions found across a package. + Iterable get functions => + libraries.expand((library) => library.functions); +} + /// Extension methods just for tests. extension on ResourceProvider { Future writeDartdocResource(String resourcePath, String content) async {