From 2559a774a3231378256cb343de32f50f7bf33e5b Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 3 Apr 2024 15:21:51 -0700 Subject: [PATCH] Rewrite the 'exported extension method' tests; add basic extension tests. (#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. --- test/dartdoc_test_base.dart | 52 +----------- test/extension_methods_test.dart | 116 --------------------------- test/extensions_test.dart | 133 +++++++++++++++++++++++++++++++ test/src/utils.dart | 19 +++-- 4 files changed, 148 insertions(+), 172 deletions(-) delete mode 100644 test/extension_methods_test.dart create mode 100644 test/extensions_test.dart 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 {