blob: 10e4364f568025b2debd74556bcadf8437201a6f [file] [log] [blame]
// Copyright (c) 2015, 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.
library dartdoc.model_test;
import 'dart:io';
import 'package:dartdoc/dartdoc.dart';
import 'package:dartdoc/src/model.dart';
import 'package:dartdoc/src/special_elements.dart';
import 'package:dartdoc/src/warnings.dart';
import 'package:test/test.dart';
import 'src/utils.dart' as utils;
/// For testing sort behavior.
class TestLibraryContainer extends LibraryContainer with Nameable {
@override
final List<String> containerOrder;
@override
String enclosingName;
@override
final String name;
@override
bool get isSdk => false;
@override
final PackageGraph packageGraph = null;
TestLibraryContainer(
this.name, this.containerOrder, LibraryContainer enclosingContainer) {
enclosingName = enclosingContainer?.name;
}
}
class TestLibraryContainerSdk extends TestLibraryContainer {
TestLibraryContainerSdk(String name, List<String> containerOrder,
LibraryContainer enclosingContainer)
: super(name, containerOrder, enclosingContainer);
@override
bool get isSdk => true;
}
void main() {
Directory sdkDir = defaultSdkDir;
if (sdkDir == null) {
print("Warning: unable to locate the Dart SDK.");
exit(1);
}
PackageGraph packageGraph;
PackageGraph packageGraphSmall;
PackageGraph packageGraphErrors;
PackageGraph packageGraphExperiments;
PackageGraph ginormousPackageGraph;
Library exLibrary;
Library fakeLibrary;
Library errorLibrary;
Library twoExportsLib;
Library interceptorsLib;
Library baseClassLib;
PackageGraph sdkAsPackageGraph;
Library dartAsync;
setUpAll(() async {
await utils.init();
packageGraph = utils.testPackageGraph;
packageGraphSmall = utils.testPackageGraphSmall;
packageGraphErrors = utils.testPackageGraphErrors;
packageGraphExperiments = utils.testPackageGraphExperiments;
ginormousPackageGraph = utils.testPackageGraphGinormous;
exLibrary = packageGraph.libraries.firstWhere((lib) => lib.name == 'ex');
errorLibrary = packageGraphErrors.libraries
.firstWhere((lib) => lib.name == 'doc_errors');
fakeLibrary =
packageGraph.libraries.firstWhere((lib) => lib.name == 'fake');
dartAsync =
packageGraph.libraries.firstWhere((lib) => lib.name == 'dart:async');
twoExportsLib =
packageGraph.libraries.firstWhere((lib) => lib.name == 'two_exports');
interceptorsLib = packageGraph.libraries
.firstWhere((lib) => lib.name == 'dart:_interceptors');
baseClassLib =
packageGraph.libraries.firstWhere((lib) => lib.name == 'base_class');
sdkAsPackageGraph = utils.testPackageGraphSdk;
});
// Experimental features not yet enabled by default. Move tests out of this block
// when the feature is enabled by default.
group('Experiments', () {
Library main;
TopLevelVariable aComplexSet,
inferredTypeSet,
specifiedSet,
untypedMap,
typedSet;
setUpAll(() {
main = packageGraphExperiments.libraries
.firstWhere((lib) => lib.name == 'main');
aComplexSet = main.constants.firstWhere((v) => v.name == 'aComplexSet');
inferredTypeSet =
main.constants.firstWhere((v) => v.name == 'inferredTypeSet');
specifiedSet = main.constants.firstWhere((v) => v.name == 'specifiedSet');
untypedMap = main.constants.firstWhere((v) => v.name == 'untypedMap');
typedSet = main.constants.firstWhere((v) => v.name == 'typedSet');
});
test('Set literals test', () {
expect(aComplexSet.modelType.name, equals('Set'));
expect(aComplexSet.modelType.typeArguments.map((a) => a.name).toList(),
equals(['AClassContainingLiterals']));
expect(aComplexSet.constantValue,
equals('const {const AClassContainingLiterals(3, 5)}'));
expect(inferredTypeSet.modelType.name, equals('Set'));
expect(
inferredTypeSet.modelType.typeArguments.map((a) => a.name).toList(),
equals(['num']));
expect(inferredTypeSet.constantValue, equals('const {1, 2.5, 3}'));
expect(specifiedSet.modelType.name, equals('Set'));
expect(specifiedSet.modelType.typeArguments.map((a) => a.name).toList(),
equals(['int']));
expect(specifiedSet.constantValue, equals('const {}'));
expect(untypedMap.modelType.name, equals('Map'));
expect(untypedMap.modelType.typeArguments.map((a) => a.name).toList(),
equals(['dynamic', 'dynamic']));
expect(untypedMap.constantValue, equals('const {}'));
expect(typedSet.modelType.name, equals('Set'));
expect(typedSet.modelType.typeArguments.map((a) => a.name).toList(),
equals(['String']));
expect(typedSet.constantValue,
matches(RegExp(r'const &lt;String&gt;\s?{}')));
});
});
group('Tools', () {
Class toolUser;
Class _NonCanonicalToolUser, CanonicalToolUser, PrivateLibraryToolUser;
Class ImplementingClassForTool, CanonicalPrivateInheritedToolUser;
Method invokeTool;
Method invokeToolNoInput;
Method invokeToolMultipleSections;
Method invokeToolNonCanonical, invokeToolNonCanonicalSubclass;
Method invokeToolPrivateLibrary, invokeToolPrivateLibraryOriginal;
Method invokeToolParentDoc, invokeToolParentDocOriginal;
final RegExp packageInvocationIndexRegexp =
RegExp(r'PACKAGE_INVOCATION_INDEX: (\d+)');
setUpAll(() {
_NonCanonicalToolUser = fakeLibrary.allClasses
.firstWhere((c) => c.name == '_NonCanonicalToolUser');
CanonicalToolUser = fakeLibrary.allClasses
.firstWhere((c) => c.name == 'CanonicalToolUser');
PrivateLibraryToolUser = fakeLibrary.allClasses
.firstWhere((c) => c.name == 'PrivateLibraryToolUser');
ImplementingClassForTool = fakeLibrary.allClasses
.firstWhere((c) => c.name == 'ImplementingClassForTool');
CanonicalPrivateInheritedToolUser = fakeLibrary.allClasses
.firstWhere((c) => c.name == 'CanonicalPrivateInheritedToolUser');
toolUser = exLibrary.classes.firstWhere((c) => c.name == 'ToolUser');
invokeTool =
toolUser.allInstanceMethods.firstWhere((m) => m.name == 'invokeTool');
invokeToolNonCanonical = _NonCanonicalToolUser.allInstanceMethods
.firstWhere((m) => m.name == 'invokeToolNonCanonical');
invokeToolNonCanonicalSubclass = CanonicalToolUser.allInstanceMethods
.firstWhere((m) => m.name == 'invokeToolNonCanonical');
invokeToolNoInput = toolUser.allInstanceMethods
.firstWhere((m) => m.name == 'invokeToolNoInput');
invokeToolMultipleSections = toolUser.allInstanceMethods
.firstWhere((m) => m.name == 'invokeToolMultipleSections');
invokeToolPrivateLibrary = PrivateLibraryToolUser.allInstanceMethods
.firstWhere((m) => m.name == 'invokeToolPrivateLibrary');
invokeToolPrivateLibraryOriginal =
(invokeToolPrivateLibrary.definingEnclosingElement as Class)
.allInstanceMethods
.firstWhere((m) => m.name == 'invokeToolPrivateLibrary');
invokeToolParentDoc = CanonicalPrivateInheritedToolUser.allInstanceMethods
.firstWhere((m) => m.name == 'invokeToolParentDoc');
invokeToolParentDocOriginal = ImplementingClassForTool.allInstanceMethods
.firstWhere((m) => m.name == 'invokeToolParentDoc');
packageGraph.allLocalModelElements.forEach((m) => m.documentation);
});
test(
'invokes tool when inherited documentation is the only means for it to be seen',
() {
// Verify setup of the test is correct.
expect(invokeToolParentDoc.isCanonical, isTrue);
expect(invokeToolParentDoc.documentationComment, isNull);
// Error message here might look strange due to toString() on Methods, but if this
// fails that means we don't have the correct invokeToolParentDoc instance.
expect(invokeToolParentDoc.documentationFrom,
contains(invokeToolParentDocOriginal));
// Tool should be substituted out here.
expect(invokeToolParentDoc.documentation, isNot(contains('{@tool')));
});
group('does _not_ invoke a tool multiple times unnecessarily', () {
test('non-canonical subclass case', () {
expect(invokeToolNonCanonical.isCanonical, isFalse);
expect(invokeToolNonCanonicalSubclass.isCanonical, isTrue);
expect(
packageInvocationIndexRegexp
.firstMatch(invokeToolNonCanonical.documentation)
.group(1),
equals(packageInvocationIndexRegexp
.firstMatch(invokeToolNonCanonicalSubclass.documentation)
.group(1)));
expect(
invokeToolPrivateLibrary.documentation, isNot(contains('{@tool')));
expect(
invokeToolPrivateLibraryOriginal.documentation, contains('{@tool'));
});
test('Documentation borrowed from implementer case', () {
expect(
packageInvocationIndexRegexp
.firstMatch(invokeToolParentDoc.documentation)
.group(1),
equals(packageInvocationIndexRegexp
.firstMatch(invokeToolParentDocOriginal.documentation)
.group(1)));
});
});
test('can invoke a tool and pass args and environment', () {
expect(invokeTool.documentation, contains('--file=<INPUT_FILE>'));
expect(invokeTool.documentation,
contains(RegExp(r'--source=lib[/\\]example\.dart_[0-9]+_[0-9]+, ')));
expect(invokeTool.documentation,
contains(RegExp(r'--package-path=<PACKAGE_PATH>, ')));
expect(
invokeTool.documentation, contains('--package-name=test_package, '));
expect(invokeTool.documentation, contains('--library-name=ex, '));
expect(invokeTool.documentation,
contains('--element-name=ToolUser.invokeTool, '));
expect(invokeTool.documentation,
contains(r'''--special= |\[]!@#\"'$%^&*()_+]'''));
expect(invokeTool.documentation, contains('INPUT: <INPUT_FILE>'));
expect(invokeTool.documentation,
contains(RegExp('SOURCE_COLUMN: [0-9]+, ')));
expect(invokeTool.documentation,
contains(RegExp(r'SOURCE_PATH: lib[/\\]example\.dart, ')));
expect(invokeTool.documentation,
contains(RegExp(r'PACKAGE_PATH: <PACKAGE_PATH>, ')));
expect(
invokeTool.documentation, contains('PACKAGE_NAME: test_package, '));
expect(invokeTool.documentation, contains('LIBRARY_NAME: ex, '));
expect(invokeTool.documentation,
contains('ELEMENT_NAME: ToolUser.invokeTool, '));
expect(invokeTool.documentation,
contains(RegExp('INVOCATION_INDEX: [0-9]+}')));
expect(invokeTool.documentation, contains('## `Yes it is a [Dog]!`'));
});
test('can invoke a tool and add a reference link', () {
expect(invokeTool.documentation,
contains('Yes it is a [Dog]! Is not a [ToolUser].'));
expect(invokeTool.documentationAsHtml,
contains('<a href="ex/ToolUser-class.html">ToolUser</a>'));
expect(invokeTool.documentationAsHtml,
contains('<a href="ex/Dog-class.html">Dog</a>'));
});
test(r'can invoke a tool with no $INPUT or args', () {
expect(invokeToolNoInput.documentation, contains('Args: []'));
expect(invokeToolNoInput.documentation,
isNot(contains('This text should not appear in the output')));
expect(invokeToolNoInput.documentation, isNot(contains('[Dog]')));
expect(invokeToolNoInput.documentationAsHtml,
isNot(contains('<a href="ex/Dog-class.html">Dog</a>')));
});
test('can invoke a tool multiple times in one comment block', () {
RegExp envLine = RegExp(r'^Env: \{', multiLine: true);
expect(
envLine.allMatches(invokeToolMultipleSections.documentation).length,
equals(2));
RegExp argLine = RegExp(r'^Args: \[', multiLine: true);
expect(
argLine.allMatches(invokeToolMultipleSections.documentation).length,
equals(2));
expect(invokeToolMultipleSections.documentation,
contains('Invokes more than one tool in the same comment block.'));
expect(invokeToolMultipleSections.documentation,
contains('This text should appear in the output.'));
expect(invokeToolMultipleSections.documentation,
contains('## `This text should appear in the output.`'));
expect(invokeToolMultipleSections.documentation,
contains('This text should also appear in the output.'));
expect(invokeToolMultipleSections.documentation,
contains('## `This text should also appear in the output.`'));
});
});
group('HTML Injection when allowed', () {
Class htmlInjection;
Method injectSimpleHtml;
Method injectHtmlFromTool;
PackageGraph injectionPackageGraph;
Library injectionExLibrary;
setUpAll(() async {
injectionPackageGraph = await utils.bootBasicPackage(
'testing/test_package', ['css', 'code_in_comments', 'excluded'],
additionalArguments: ['--inject-html']);
injectionExLibrary =
injectionPackageGraph.libraries.firstWhere((lib) => lib.name == 'ex');
htmlInjection = injectionExLibrary.classes
.firstWhere((c) => c.name == 'HtmlInjection');
injectSimpleHtml = htmlInjection.allInstanceMethods
.firstWhere((m) => m.name == 'injectSimpleHtml');
injectHtmlFromTool = htmlInjection.allInstanceMethods
.firstWhere((m) => m.name == 'injectHtmlFromTool');
injectionPackageGraph.allLocalModelElements
.forEach((m) => m.documentation);
});
test("can inject HTML", () {
expect(
injectSimpleHtml.documentation,
contains(
'\n<dartdoc-html>bad2bbdd4a5cf9efb3212afff4449904756851aa</dartdoc-html>\n'));
expect(injectSimpleHtml.documentation,
isNot(contains('\n{@inject-html}\n')));
expect(injectSimpleHtml.documentation,
isNot(contains('\n{@end-inject-html}\n')));
expect(injectSimpleHtml.documentationAsHtml,
contains(' <div style="opacity: 0.5;">[HtmlInjection]</div>'));
});
test("can inject HTML from tool", () {
RegExp envLine = RegExp(r'^Env: \{', multiLine: true);
expect(envLine.allMatches(injectHtmlFromTool.documentation).length,
equals(2));
RegExp argLine = RegExp(r'^Args: \[', multiLine: true);
expect(argLine.allMatches(injectHtmlFromTool.documentation).length,
equals(2));
expect(
injectHtmlFromTool.documentation,
contains(
'Invokes more than one tool in the same comment block, and injects HTML.'));
expect(injectHtmlFromTool.documentationAsHtml,
contains('<div class="title">Title</div>'));
expect(injectHtmlFromTool.documentationAsHtml,
isNot(contains('{@inject-html}')));
expect(injectHtmlFromTool.documentationAsHtml,
isNot(contains('{@end-inject-html}')));
});
});
group('HTML Injection when not allowed', () {
Class htmlInjection;
Method injectSimpleHtml;
setUpAll(() {
htmlInjection =
exLibrary.classes.firstWhere((c) => c.name == 'HtmlInjection');
injectSimpleHtml = htmlInjection.allInstanceMethods
.firstWhere((m) => m.name == 'injectSimpleHtml');
});
test("doesn't inject HTML if --inject-html option is not present", () {
expect(
injectSimpleHtml.documentation,
isNot(contains(
'\n<dartdoc-html>bad2bbdd4a5cf9efb3212afff4449904756851aa</dartdoc-html>\n')));
expect(injectSimpleHtml.documentation, isNot(contains('<dartdoc-html>')));
expect(injectSimpleHtml.documentationAsHtml, contains('{@inject-html}'));
});
});
group('Missing and Remote', () {
test('Verify that SDK libraries are not canonical when missing', () {
expect(
dartAsync.package.documentedWhere, equals(DocumentLocation.missing));
expect(dartAsync.isCanonical, isFalse);
expect(ginormousPackageGraph.publicPackages, isNotEmpty);
});
test(
'Verify that autoIncludeDependencies makes everything document locally',
() {
expect(ginormousPackageGraph.packages.map((p) => p.documentedWhere),
everyElement((x) => x == DocumentLocation.local));
});
test('Verify that ginormousPackageGraph takes in the SDK', () {
expect(
ginormousPackageGraph.packages
.firstWhere((p) => p.isSdk)
.libraries
.length,
greaterThan(1));
expect(
ginormousPackageGraph.packages
.firstWhere((p) => p.isSdk)
.documentedWhere,
equals(DocumentLocation.local));
});
test('Verify that packageGraph has an SDK but will not document it locally',
() {
expect(packageGraph.packages.firstWhere((p) => p.isSdk).documentedWhere,
isNot(equals(DocumentLocation.local)));
});
});
group('Category', () {
test(
'Verify auto-included dependencies do not use default package category definitions',
() {
Class IAmAClassWithCategories = ginormousPackageGraph.localPackages
.firstWhere((Package p) => p.name == 'test_package_imported')
.publicLibraries
.firstWhere((Library l) => l.name == 'categoriesExported')
.publicClasses
.firstWhere((Class c) => c.name == 'IAmAClassWithCategories');
expect(IAmAClassWithCategories.hasCategoryNames, isTrue);
expect(IAmAClassWithCategories.categories.length, equals(1));
expect(
IAmAClassWithCategories.categories.first.name, equals('Excellent'));
expect(IAmAClassWithCategories.displayedCategories, isEmpty);
});
// For flutter, we allow reexports to pick up categories from the package
// they are exposed in.
test('Verify that reexported classes pick up categories', () {
Class IAmAClassWithCategoriesReexport = ginormousPackageGraph
.localPackages
.firstWhere((Package p) => p.name == 'test_package')
.publicLibraries
.firstWhere((Library l) => l.name == 'fake')
.publicClasses
.firstWhere((Class c) => c.name == 'IAmAClassWithCategories');
expect(IAmAClassWithCategoriesReexport.hasCategoryNames, isTrue);
expect(IAmAClassWithCategoriesReexport.categories.length, equals(1));
expect(IAmAClassWithCategoriesReexport.categories.first.name,
equals('Superb'));
expect(IAmAClassWithCategoriesReexport.displayedCategories, isNotEmpty);
Category category =
IAmAClassWithCategoriesReexport.displayedCategories.first;
expect(category.spanClass, equals('superb'));
expect(category.categoryNumberClass, equals('cp-0'));
expect(category.isDocumented, isTrue);
});
test('Verify that multiple categories work correctly', () {
Library fakeLibrary = ginormousPackageGraph.localPackages
.firstWhere((Package p) => p.name == 'test_package')
.publicLibraries
.firstWhere((Library l) => l.name == 'fake');
Class BaseForDocComments = fakeLibrary.publicClasses
.firstWhere((Class c) => c.name == 'BaseForDocComments');
Class SubForDocComments = fakeLibrary.publicClasses
.firstWhere((Class c) => c.name == 'SubForDocComments');
expect(BaseForDocComments.hasCategoryNames, isTrue);
// Display both, with the correct order and display name.
expect(BaseForDocComments.displayedCategories.length, equals(2));
expect(
BaseForDocComments.displayedCategories.first.name, equals('Superb'));
expect(
BaseForDocComments.displayedCategories.last.name, equals('Unreal'));
// Subclasses do not inherit category information.
expect(SubForDocComments.hasCategoryNames, isTrue);
expect(SubForDocComments.categories, hasLength(1));
expect(SubForDocComments.categories.first.isDocumented, isFalse);
expect(SubForDocComments.displayedCategories, isEmpty);
});
test('Verify categories for test_package', () {
expect(packageGraph.localPackages.length, equals(1));
expect(packageGraph.localPackages.first.hasCategories, isTrue);
List<Category> packageCategories =
packageGraph.localPackages.first.categories;
expect(packageCategories.length, equals(6));
expect(
packageGraph.localPackages.first.categoriesWithPublicLibraries.length,
equals(3));
expect(
packageCategories.map((c) => c.name).toList(),
orderedEquals([
'Superb',
'Unreal',
'Real Libraries',
'Misc',
'More Excellence',
'NotSoExcellent'
]));
expect(packageCategories.map((c) => c.libraries.length).toList(),
orderedEquals([0, 2, 3, 1, 0, 0]));
expect(
packageGraph
.localPackages.first.defaultCategory.publicLibraries.length,
equals(7));
});
test('Verify libraries with multiple categories show up in multiple places',
() {
List<Category> packageCategories =
packageGraph.publicPackages.first.categories;
Category realLibraries =
packageCategories.firstWhere((c) => c.name == 'Real Libraries');
Category misc = packageCategories.firstWhere((c) => c.name == 'Misc');
expect(
realLibraries.libraries.map((l) => l.name), contains('two_exports'));
expect(misc.libraries.map((l) => l.name), contains('two_exports'));
});
test('Verify that packages without categories get handled', () {
expect(packageGraphSmall.localPackages.length, equals(1));
expect(packageGraphSmall.localPackages.first.hasCategories, isFalse);
List<Category> packageCategories =
packageGraphSmall.localPackages.first.categories;
expect(packageCategories.length, equals(0));
expect(
packageGraph
.localPackages.first.defaultCategory.publicLibraries.length,
equals(7));
});
});
group('LibraryContainer', () {
TestLibraryContainer topLevel;
List<String> sortOrderBasic;
List<String> containerNames;
setUpAll(() {
topLevel = TestLibraryContainer('topLevel', [], null);
sortOrderBasic = ['theFirst', 'second', 'fruit'];
containerNames = [
'moo',
'woot',
'theFirst',
'topLevel Things',
'toplevel',
'fruit'
];
});
test('multiple containers with specified sort order', () {
List<LibraryContainer> containers = [];
for (String name in containerNames) {
containers.add(TestLibraryContainer(name, sortOrderBasic, topLevel));
}
containers.add(TestLibraryContainerSdk('SDK', sortOrderBasic, topLevel));
containers.sort();
expect(
containers.map((c) => c.name),
orderedEquals([
'theFirst',
'fruit',
'toplevel',
'SDK',
'topLevel Things',
'moo',
'woot'
]));
});
test('multiple containers, no specified sort order', () {
List<LibraryContainer> containers = [];
for (String name in containerNames) {
containers.add(TestLibraryContainer(name, [], topLevel));
}
containers.add(TestLibraryContainerSdk('SDK', [], topLevel));
containers.sort();
expect(
containers.map((c) => c.name),
orderedEquals([
'toplevel',
'SDK',
'topLevel Things',
'fruit',
'moo',
'theFirst',
'woot'
]));
});
});
group('Package', () {
group('test package', () {
test('name', () {
expect(packageGraph.defaultPackage.name, 'test_package');
});
test('libraries', () {
expect(packageGraph.localPublicLibraries, hasLength(12));
expect(interceptorsLib.isPublic, isFalse);
});
test('homepage', () {
expect(packageGraph.defaultPackage.hasHomepage, true);
expect(packageGraph.defaultPackage.homepage,
equals('http://github.com/dart-lang'));
});
test('packages', () {
expect(packageGraph.localPackages, hasLength(1));
Package package = packageGraph.localPackages.first;
expect(package.name, 'test_package');
expect(package.publicLibraries, hasLength(12));
});
test('multiple packages, sorted default', () {
expect(ginormousPackageGraph.localPackages, hasLength(5));
expect(ginormousPackageGraph.localPackages.first.name,
equals('test_package'));
});
test('is documented in library', () {
expect(exLibrary.isDocumented, isTrue);
});
test('has documentation', () {
expect(packageGraph.defaultPackage.hasDocumentationFile, isTrue);
expect(packageGraph.defaultPackage.hasDocumentation, isTrue);
});
test('documentation exists', () {
expect(
packageGraph.defaultPackage.documentation
.startsWith('# Best Package'),
isTrue);
});
test('documentation can be rendered as HTML', () {
expect(packageGraph.defaultPackage.documentationAsHtml,
contains('<h1 id="best-package">Best Package</h1>'));
});
test('sdk name', () {
expect(sdkAsPackageGraph.defaultPackage.name, equals('Dart'));
expect(sdkAsPackageGraph.defaultPackage.kind, equals('SDK'));
});
test('sdk homepage', () {
expect(sdkAsPackageGraph.defaultPackage.hasHomepage, isTrue);
expect(sdkAsPackageGraph.defaultPackage.homepage,
equals('https://github.com/dart-lang/sdk'));
});
test('sdk version', () {
expect(sdkAsPackageGraph.defaultPackage.version, isNotNull);
});
test('sdk description', () {
expect(sdkAsPackageGraph.defaultPackage.documentation,
startsWith('Welcome to the Dart API reference doc'));
});
test('has anonymous libraries', () {
expect(
packageGraph.libraries
.where((lib) => lib.name == 'anonymous_library'),
hasLength(1));
expect(
packageGraph.libraries
.where((lib) => lib.name == 'another_anonymous_lib'),
hasLength(1));
});
});
group('test small package', () {
test('does not have documentation', () {
expect(utils.testPackageGraphSmall.defaultPackage.hasDocumentation,
isFalse);
expect(utils.testPackageGraphSmall.defaultPackage.hasDocumentationFile,
isFalse);
expect(utils.testPackageGraphSmall.defaultPackage.documentationFile,
isNull);
expect(
utils.testPackageGraphSmall.defaultPackage.documentation, isNull);
});
});
group('SDK-specific cases', () {
test('Verify Interceptor is hidden from inheritance in docs', () {
Library htmlLibrary = sdkAsPackageGraph.libraries
.singleWhere((l) => l.name == 'dart:html');
Class EventTarget =
htmlLibrary.allClasses.singleWhere((c) => c.name == 'EventTarget');
Field hashCode = EventTarget.allPublicInstanceProperties
.singleWhere((f) => f.name == 'hashCode');
Class objectModelElement =
sdkAsPackageGraph.specialClasses[SpecialClass.object];
// If this fails, EventTarget might have been changed to no longer
// inherit from Interceptor. If that's true, adjust test case to
// another class that does.
expect(
hashCode.inheritance.any((c) => c.name == 'Interceptor'), isTrue);
// If EventTarget really does start implementing hashCode, this will
// fail.
expect(hashCode.href, equals('dart-core/Object/hashCode.html'));
expect(hashCode.canonicalEnclosingElement, equals(objectModelElement));
expect(
EventTarget.publicSuperChainReversed
.any((et) => et.name == 'Interceptor'),
isFalse);
});
test('Verify pragma is hidden in docs', () {
Class pragmaModelElement =
sdkAsPackageGraph.specialClasses[SpecialClass.pragma];
Class HasPragma = fakeLibrary.allClasses
.firstWhere((Class c) => c.name == 'HasPragma');
expect(pragmaModelElement.name, equals('pragma'));
expect(HasPragma.annotations, isEmpty);
});
});
});
group('Library', () {
Library dartAsyncLib,
anonLib,
isDeprecated,
someLib,
reexportOneLib,
reexportTwoLib;
Class SomeClass, SomeOtherClass, YetAnotherClass, AUnicornClass;
setUpAll(() {
dartAsyncLib = utils.testPackageGraphSdk.libraries
.firstWhere((l) => l.name == 'dart:async');
anonLib = packageGraph.libraries
.firstWhere((lib) => lib.name == 'anonymous_library');
someLib = packageGraph.allLibraries.values
.firstWhere((lib) => lib.name == 'reexport.somelib');
reexportOneLib = packageGraph.libraries
.firstWhere((lib) => lib.name == 'reexport_one');
reexportTwoLib = packageGraph.libraries
.firstWhere((lib) => lib.name == 'reexport_two');
SomeClass = someLib.getClassByName('SomeClass');
SomeOtherClass = someLib.getClassByName('SomeOtherClass');
YetAnotherClass = someLib.getClassByName('YetAnotherClass');
AUnicornClass = someLib.getClassByName('AUnicornClass');
isDeprecated = packageGraph.libraries
.firstWhere((lib) => lib.name == 'is_deprecated');
// Make sure the first library is dart:async
expect(dartAsyncLib.name, 'dart:async');
});
test('has a name', () {
expect(exLibrary.name, 'ex');
});
test('packageName', () {
expect(exLibrary.packageName, 'test_package');
});
test('has a fully qualified name', () {
expect(exLibrary.fullyQualifiedName, 'ex');
});
test('can be deprecated', () {
expect(isDeprecated.isDeprecated, isTrue);
expect(anonLib.isDeprecated, isFalse);
});
test('can be reexported even if the file suffix is not .dart', () {
expect(fakeLibrary.allClasses.map((c) => c.name),
contains('MyClassFromADartFile'));
});
test('that is deprecated has a deprecated css class in linkedName', () {
expect(isDeprecated.linkedName, contains('class="deprecated"'));
});
test('sdk library have formatted names', () {
expect(dartAsyncLib.name, 'dart:async');
expect(dartAsyncLib.dirName, 'dart-async');
expect(dartAsyncLib.href, 'dart-async/dart-async-library.html');
});
test('has documentation', () {
expect(exLibrary.documentation,
'a library. testing string escaping: `var s = \'a string\'` <cool>\n');
});
test('has one line docs', () {
expect(
fakeLibrary.oneLineDoc,
equals(
'WOW FAKE PACKAGE IS <strong>BEST</strong> <a href="http://example.org">PACKAGE</a> <a href="fake/fake-library.html">[...]</a>'));
});
test('has properties', () {
expect(exLibrary.hasPublicProperties, isTrue);
});
test('has constants', () {
expect(exLibrary.hasPublicConstants, isTrue);
});
test('has exceptions', () {
expect(exLibrary.hasPublicExceptions, isTrue);
});
test('has mixins', () {
expect(fakeLibrary.hasPublicMixins, isTrue);
expect(reexportTwoLib.hasPublicMixins, isTrue);
});
test('has enums', () {
expect(exLibrary.hasPublicEnums, isTrue);
});
test('has functions', () {
expect(exLibrary.hasPublicFunctions, isTrue);
});
test('has typedefs', () {
expect(exLibrary.hasPublicTypedefs, isTrue);
});
test('exported class', () {
expect(exLibrary.classes.any((c) => c.name == 'Helper'), isTrue);
});
test('exported function', () {
expect(
exLibrary.functions.any((f) => f.name == 'helperFunction'), isFalse);
});
test('anonymous lib', () {
expect(anonLib.isAnonymous, isTrue);
});
test('with ambiguous reexport warnings', () {
final warningMsg =
'(reexport_one, reexport_two) -> reexport_two (confidence 0.000)';
// Unicorn class has a warning because two @canonicalFors cancel each other out.
expect(
packageGraph.packageWarningCounter.hasWarning(
AUnicornClass, PackageWarning.ambiguousReexport, warningMsg),
isTrue);
// This class is ambiguous without a @canonicalFor
expect(
packageGraph.packageWarningCounter.hasWarning(
YetAnotherClass, PackageWarning.ambiguousReexport, warningMsg),
isTrue);
// These two classes have a @canonicalFor
expect(
packageGraph.packageWarningCounter.hasWarning(
SomeClass, PackageWarning.ambiguousReexport, warningMsg),
isFalse);
expect(
packageGraph.packageWarningCounter.hasWarning(
SomeOtherClass, PackageWarning.ambiguousReexport, warningMsg),
isFalse);
// This library has a canonicalFor with no corresponding item
expect(
packageGraph.packageWarningCounter.hasWarning(reexportTwoLib,
PackageWarning.ignoredCanonicalFor, 'something.ThatDoesntExist'),
isTrue);
});
test('@canonicalFor directive works', () {
expect(SomeOtherClass.canonicalLibrary, reexportOneLib);
expect(SomeClass.canonicalLibrary, reexportTwoLib);
});
});
group('Macros', () {
Class dog;
Enum MacrosFromAccessors;
Method withMacro, withMacro2, withPrivateMacro, withUndefinedMacro;
EnumField macroReferencedHere;
setUpAll(() {
dog = exLibrary.classes.firstWhere((c) => c.name == 'Dog');
withMacro =
dog.allInstanceMethods.firstWhere((m) => m.name == 'withMacro');
withMacro2 =
dog.allInstanceMethods.firstWhere((m) => m.name == 'withMacro2');
withPrivateMacro = dog.allInstanceMethods
.firstWhere((m) => m.name == 'withPrivateMacro');
withUndefinedMacro = dog.allInstanceMethods
.firstWhere((m) => m.name == 'withUndefinedMacro');
MacrosFromAccessors =
fakeLibrary.enums.firstWhere((e) => e.name == 'MacrosFromAccessors');
macroReferencedHere = MacrosFromAccessors.publicConstants
.firstWhere((e) => e.name == 'macroReferencedHere');
});
test("renders a macro defined within a enum", () {
expect(macroReferencedHere.documentationAsHtml,
contains('This is a macro defined in an Enum accessor.'));
});
test("renders a macro within the same comment where it's defined", () {
expect(withMacro.documentation,
equals("Macro method\n\nFoo macro content\nMore docs"));
});
test("renders a macro in another method, not the same where it's defined",
() {
expect(withMacro2.documentation, equals("Foo macro content"));
});
test("renders a macro defined in a private symbol", () {
expect(withPrivateMacro.documentation, contains("Private macro content"));
});
test("a warning is generated for unknown macros", () {
// Retrieve documentation first to generate the warning.
withUndefinedMacro.documentation;
expect(
packageGraph.packageWarningCounter.hasWarning(withUndefinedMacro,
PackageWarning.unknownMacro, 'ThatDoesNotExist'),
isTrue);
});
});
group('YouTube Errors', () {
Class documentationErrors;
Method withYouTubeWrongParams;
Method withYouTubeBadWidth;
Method withYouTubeBadHeight;
Method withYouTubeInvalidUrl;
Method withYouTubeUrlWithAdditionalParameters;
setUpAll(() {
documentationErrors = errorLibrary.classes
.firstWhere((c) => c.name == 'DocumentationErrors')
..documentation;
withYouTubeWrongParams = documentationErrors.allInstanceMethods
.firstWhere((m) => m.name == 'withYouTubeWrongParams')
..documentation;
withYouTubeBadWidth = documentationErrors.allInstanceMethods
.firstWhere((m) => m.name == 'withYouTubeBadWidth')
..documentation;
withYouTubeBadHeight = documentationErrors.allInstanceMethods
.firstWhere((m) => m.name == 'withYouTubeBadHeight')
..documentation;
withYouTubeInvalidUrl = documentationErrors.allInstanceMethods
.firstWhere((m) => m.name == 'withYouTubeInvalidUrl')
..documentation;
withYouTubeUrlWithAdditionalParameters = documentationErrors
.allInstanceMethods
.firstWhere((m) => m.name == 'withYouTubeUrlWithAdditionalParameters')
..documentation;
});
test("warns on youtube video with missing parameters", () {
expect(
packageGraphErrors.packageWarningCounter.hasWarning(
withYouTubeWrongParams,
PackageWarning.invalidParameter,
'Invalid @youtube directive, "{@youtube https://youtu.be/oHg5SJYRHA0}"\n'
'YouTube directives must be of the form "{@youtube WIDTH HEIGHT URL}"'),
isTrue);
});
test("warns on youtube video with non-integer width", () {
expect(
packageGraphErrors.packageWarningCounter.hasWarning(
withYouTubeBadWidth,
PackageWarning.invalidParameter,
'A @youtube directive has an invalid width, "100px". The width '
'must be a positive integer.'),
isTrue);
});
test("warns on youtube video with non-integer height", () {
expect(
packageGraphErrors.packageWarningCounter.hasWarning(
withYouTubeBadHeight,
PackageWarning.invalidParameter,
'A @youtube directive has an invalid height, "100px". The height '
'must be a positive integer.'),
isTrue);
});
test("warns on youtube video with invalid video URL", () {
expect(
packageGraphErrors.packageWarningCounter.hasWarning(
withYouTubeInvalidUrl,
PackageWarning.invalidParameter,
'A @youtube directive has an invalid URL: '
'"http://host/path/to/video.mp4". Supported YouTube URLs have '
'the follwing format: '
'https://www.youtube.com/watch?v=oHg5SJYRHA0.'),
isTrue);
});
test("warns on youtube video with extra parameters in URL", () {
expect(
packageGraphErrors.packageWarningCounter.hasWarning(
withYouTubeUrlWithAdditionalParameters,
PackageWarning.invalidParameter,
'A @youtube directive has an invalid URL: '
'"https://www.youtube.com/watch?v=yI-8QHpGIP4&list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG&index=5". '
'Supported YouTube URLs have the follwing format: '
'https://www.youtube.com/watch?v=oHg5SJYRHA0.'),
isTrue);
});
});
group('YouTube', () {
Class dog;
Method withYouTubeWatchUrl;
Method withYouTubeInOneLineDoc;
Method withYouTubeInline;
setUpAll(() {
dog = exLibrary.classes.firstWhere((c) => c.name == 'Dog');
withYouTubeWatchUrl = dog.allInstanceMethods
.firstWhere((m) => m.name == 'withYouTubeWatchUrl');
withYouTubeInOneLineDoc = dog.allInstanceMethods
.firstWhere((m) => m.name == 'withYouTubeInOneLineDoc');
withYouTubeInline = dog.allInstanceMethods
.firstWhere((m) => m.name == 'withYouTubeInline');
});
test(
"renders a YouTube video within the method documentation with correct aspect ratio",
() {
expect(
withYouTubeWatchUrl.documentation,
contains(
'<iframe src="https://www.youtube.com/embed/oHg5SJYRHA0?rel=0"'));
// Video is 560x315, which means height is 56.25% of width.
expect(
withYouTubeWatchUrl.documentation, contains('padding-top: 56.25%;'));
});
test("Doesn't place YouTube video in one line doc", () {
expect(
withYouTubeInOneLineDoc.oneLineDoc,
isNot(contains(
'<iframe src="https://www.youtube.com/embed/oHg5SJYRHA0?rel=0"')));
expect(
withYouTubeInOneLineDoc.documentation,
contains(
'<iframe src="https://www.youtube.com/embed/oHg5SJYRHA0?rel=0"'));
});
test("Handles YouTube video inline properly", () {
// Make sure it doesn't have a double-space before the continued line,
// which would indicate to Markdown to indent the line.
expect(withYouTubeInline.documentation, isNot(contains(' works')));
});
});
group('Animation Errors', () {
Class documentationErrors;
Method withInvalidNamedAnimation;
Method withAnimationNonUnique;
Method withAnimationNonUniqueDeprecated;
Method withAnimationWrongParams;
Method withAnimationBadWidth;
Method withAnimationBadHeight;
Method withAnimationUnknownArg;
setUpAll(() {
documentationErrors = errorLibrary.classes
.firstWhere((c) => c.name == 'DocumentationErrors')
..documentation;
withInvalidNamedAnimation = documentationErrors.allInstanceMethods
.firstWhere((m) => m.name == 'withInvalidNamedAnimation')
..documentation;
withAnimationNonUnique = documentationErrors.allInstanceMethods
.firstWhere((m) => m.name == 'withAnimationNonUnique')
..documentation;
withAnimationNonUniqueDeprecated = documentationErrors.allInstanceMethods
.firstWhere((m) => m.name == 'withAnimationNonUniqueDeprecated')
..documentation;
withAnimationWrongParams = documentationErrors.allInstanceMethods
.firstWhere((m) => m.name == 'withAnimationWrongParams')
..documentation;
withAnimationBadWidth = documentationErrors.allInstanceMethods
.firstWhere((m) => m.name == 'withAnimationBadWidth')
..documentation;
withAnimationBadHeight = documentationErrors.allInstanceMethods
.firstWhere((m) => m.name == 'withAnimationBadHeight')
..documentation;
withAnimationUnknownArg = documentationErrors.allInstanceMethods
.firstWhere((m) => m.name == 'withAnimationUnknownArg')
..documentation;
});
test("warns with invalidly-named animation within the method documentation",
() {
expect(
packageGraphErrors.packageWarningCounter.hasWarning(
withInvalidNamedAnimation,
PackageWarning.invalidParameter,
'An animation has an invalid identifier, "2isNot-A-ValidName". '
'The identifier can only contain letters, numbers and '
'underscores, and must not begin with a number.'),
isTrue);
});
test("warns on a non-unique animation name within a method", () {
expect(
packageGraphErrors.packageWarningCounter.hasWarning(
withAnimationNonUnique,
PackageWarning.invalidParameter,
'An animation has a non-unique identifier, "barHerderAnimation". '
'Animation identifiers must be unique.'),
isTrue);
});
test("warns on a non-unique animation name within a deprecated-form method",
() {
expect(
packageGraphErrors.packageWarningCounter.hasWarning(
withAnimationNonUniqueDeprecated,
PackageWarning.invalidParameter,
'An animation has a non-unique identifier, "fooHerderAnimation". '
'Animation identifiers must be unique.'),
isTrue);
});
test("warns on animation with missing parameters", () {
expect(
packageGraphErrors.packageWarningCounter.hasWarning(
withAnimationWrongParams,
PackageWarning.invalidParameter,
'Invalid @animation directive, "{@animation http://host/path/to/video.mp4}"\n'
'Animation directives must be of the form "{@animation WIDTH '
'HEIGHT URL [id=ID]}"'),
isTrue);
});
test("warns on animation with non-integer width", () {
expect(
packageGraphErrors.packageWarningCounter.hasWarning(
withAnimationBadWidth,
PackageWarning.invalidParameter,
'An animation has an invalid width (badWidthAnimation), "100px". '
'The width must be an integer.'),
isTrue);
});
test("warns on animation with non-integer height", () {
expect(
packageGraphErrors.packageWarningCounter.hasWarning(
withAnimationBadHeight,
PackageWarning.invalidParameter,
'An animation has an invalid height (badHeightAnimation), '
'"100px". The height must be an integer.'),
isTrue);
});
test("Unknown arguments generate an error.", () {
expect(
packageGraphErrors.packageWarningCounter.hasWarning(
withAnimationUnknownArg,
PackageWarning.invalidParameter,
'The {@animation ...} directive was called with invalid '
'parameters. FormatException: Could not find an option named "name".'),
isTrue);
});
});
group('Animation', () {
Class dog;
Method withAnimation;
Method withNamedAnimation;
Method withQuoteNamedAnimation;
Method withDeprecatedAnimation;
Method withAnimationInOneLineDoc;
Method withAnimationInline;
Method withAnimationOutOfOrder;
setUpAll(() {
dog = exLibrary.classes.firstWhere((c) => c.name == 'Dog');
withAnimation =
dog.allInstanceMethods.firstWhere((m) => m.name == 'withAnimation');
withNamedAnimation = dog.allInstanceMethods
.firstWhere((m) => m.name == 'withNamedAnimation');
withQuoteNamedAnimation = dog.allInstanceMethods
.firstWhere((m) => m.name == 'withQuotedNamedAnimation');
withDeprecatedAnimation = dog.allInstanceMethods
.firstWhere((m) => m.name == 'withDeprecatedAnimation');
withAnimationInOneLineDoc = dog.allInstanceMethods
.firstWhere((m) => m.name == 'withAnimationInOneLineDoc');
withAnimationInline = dog.allInstanceMethods
.firstWhere((m) => m.name == 'withAnimationInline');
withAnimationOutOfOrder = dog.allInstanceMethods
.firstWhere((m) => m.name == 'withAnimationOutOfOrder');
});
test("renders an unnamed animation within the method documentation", () {
expect(withAnimation.documentation, contains('<video id="animation_1"'));
});
test("renders a named animation within the method documentation", () {
expect(withNamedAnimation.documentation,
contains('<video id="namedAnimation"'));
});
test("renders a quoted, named animation within the method documentation",
() {
expect(withQuoteNamedAnimation.documentation,
contains('<video id="quotedNamedAnimation"'));
expect(withQuoteNamedAnimation.documentation,
contains('<video id="quotedNamedAnimation2"'));
});
test("renders a deprecated-form animation within the method documentation",
() {
expect(withDeprecatedAnimation.documentation,
contains('<video id="deprecatedAnimation"'));
expect(
packageGraph.packageWarningCounter.hasWarning(
withDeprecatedAnimation,
PackageWarning.deprecated,
'Deprecated form of @animation directive, '
'"{@animation deprecatedAnimation 100 100 http://host/path/to/video.mp4}"\n'
'Animation directives are now of the form "{@animation '
'WIDTH HEIGHT URL [id=ID]}" (id is an optional '
'parameter)'),
isTrue);
});
test("Doesn't place animations in one line doc", () {
expect(withAnimationInOneLineDoc.oneLineDoc, isNot(contains('<video')));
expect(withAnimationInOneLineDoc.documentation, contains('<video'));
});
test("Handles animations inline properly", () {
// Make sure it doesn't have a double-space before the continued line,
// which would indicate to Markdown to indent the line.
expect(withAnimationInline.documentation, isNot(contains(' works')));
});
test("Out of order arguments work.", () {
expect(withAnimationOutOfOrder.documentation,
contains('<video id="outOfOrder"'));
});
});
group('MultiplyInheritedExecutableElement handling', () {
Class BaseThingy, BaseThingy2, ImplementingThingy2;
Method aImplementingThingyMethod;
Field aImplementingThingyField;
Field aImplementingThingy;
Accessor aImplementingThingyAccessor;
setUpAll(() {
BaseThingy =
fakeLibrary.classes.firstWhere((c) => c.name == 'BaseThingy');
BaseThingy2 =
fakeLibrary.classes.firstWhere((c) => c.name == 'BaseThingy2');
ImplementingThingy2 = fakeLibrary.classes
.firstWhere((c) => c.name == 'ImplementingThingy2');
aImplementingThingy = ImplementingThingy2.allInstanceFields
.firstWhere((m) => m.name == 'aImplementingThingy');
aImplementingThingyMethod = ImplementingThingy2.allInstanceMethods
.firstWhere((m) => m.name == 'aImplementingThingyMethod');
aImplementingThingyField = ImplementingThingy2.allInstanceFields
.firstWhere((m) => m.name == 'aImplementingThingyField');
aImplementingThingyAccessor = aImplementingThingyField.getter;
});
test('Verify behavior of imperfect resolver', () {
expect(aImplementingThingy.element.enclosingElement,
equals(BaseThingy2.element));
expect(aImplementingThingyMethod.element.enclosingElement,
equals(BaseThingy.element));
expect(aImplementingThingyField.element.enclosingElement,
equals(BaseThingy.element));
expect(aImplementingThingyAccessor.element.enclosingElement,
equals(BaseThingy.element));
});
});
group('Docs as HTML', () {
Class Apple, B, superAwesomeClass, foo2;
TopLevelVariable incorrectDocReferenceFromEx;
TopLevelVariable bulletDoced;
ModelFunction thisIsAsync;
ModelFunction topLevelFunction;
Class extendedClass;
TopLevelVariable testingCodeSyntaxInOneLiners;
Class specialList;
Class baseForDocComments;
Method doAwesomeStuff;
Class subForDocComments;
ModelFunction short;
setUpAll(() {
incorrectDocReferenceFromEx = exLibrary.constants
.firstWhere((c) => c.name == 'incorrectDocReferenceFromEx');
B = exLibrary.classes.firstWhere((c) => c.name == 'B');
Apple = exLibrary.classes.firstWhere((c) => c.name == 'Apple');
specialList =
fakeLibrary.classes.firstWhere((c) => c.name == 'SpecialList');
bulletDoced =
fakeLibrary.constants.firstWhere((c) => c.name == 'bulletDoced');
topLevelFunction =
fakeLibrary.functions.firstWhere((f) => f.name == 'topLevelFunction');
thisIsAsync =
fakeLibrary.functions.firstWhere((f) => f.name == 'thisIsAsync');
testingCodeSyntaxInOneLiners = fakeLibrary.constants
.firstWhere((c) => c.name == 'testingCodeSyntaxInOneLiners');
superAwesomeClass = fakeLibrary.classes
.firstWhere((cls) => cls.name == 'SuperAwesomeClass');
foo2 = fakeLibrary.classes.firstWhere((cls) => cls.name == 'Foo2');
assert(twoExportsLib != null);
extendedClass = twoExportsLib.allClasses
.firstWhere((clazz) => clazz.name == 'ExtendingClass');
subForDocComments =
fakeLibrary.classes.firstWhere((c) => c.name == 'SubForDocComments');
baseForDocComments =
fakeLibrary.classes.firstWhere((c) => c.name == 'BaseForDocComments');
doAwesomeStuff = baseForDocComments.instanceMethods
.firstWhere((m) => m.name == 'doAwesomeStuff');
short = fakeLibrary.functions.firstWhere((f) => f.name == 'short');
});
group('markdown extensions', () {
Class DocumentWithATable;
String docsAsHtml;
setUpAll(() {
DocumentWithATable = fakeLibrary.classes
.firstWhere((cls) => cls.name == 'DocumentWithATable');
docsAsHtml = DocumentWithATable.documentationAsHtml;
});
test('Verify table appearance', () {
expect(docsAsHtml.contains('<table><thead><tr><th>Component</th>'),
isTrue);
});
test('Verify links inside of table headers', () {
expect(
docsAsHtml.contains(
'<th><a href="fake/Annotation-class.html">Annotation</a></th>'),
isTrue);
});
test('Verify links inside of table body', () {
expect(
docsAsHtml.contains(
'<tbody><tr><td><a href="fake/DocumentWithATable/foo-constant.html">foo</a></td>'),
isTrue);
});
test('Verify there is no emoji support', () {
TopLevelVariable tpvar = fakeLibrary.constants
.firstWhere((t) => t.name == 'hasMarkdownInDoc');
docsAsHtml = tpvar.documentationAsHtml;
expect(docsAsHtml.contains('3ffe:2a00:100:7031::1'), isTrue);
});
});
group('doc references', () {
String docsAsHtml;
setUpAll(() {
docsAsHtml = doAwesomeStuff.documentationAsHtml;
});
test('can handle renamed imports', () {
ModelFunction aFunctionUsingRenamedLib = fakeLibrary.functions
.firstWhere((f) => f.name == 'aFunctionUsingRenamedLib');
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to library: <a href="mylibpub/mylibpub-library.html">renamedLib</a>'));
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to constructor (implied): <a href="mylibpub/YetAnotherHelper/YetAnotherHelper.html">new renamedLib.YetAnotherHelper()</a>'));
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to constructor (implied, no new): <a href="mylibpub/YetAnotherHelper/YetAnotherHelper.html">renamedLib.YetAnotherHelper()</a>'));
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to class: <a href="mylibpub/YetAnotherHelper-class.html">renamedLib.YetAnotherHelper</a>'));
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to constructor (direct): <a href="mylibpub/YetAnotherHelper/YetAnotherHelper.html">renamedLib.YetAnotherHelper.YetAnotherHelper</a>'));
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to class member: <a href="mylibpub/YetAnotherHelper/getMoreContents.html">renamedLib.YetAnotherHelper.getMoreContents</a>'));
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to function: <a href="mylibpub/helperFunction.html">renamedLib.helperFunction</a>'));
expect(
aFunctionUsingRenamedLib.documentationAsHtml,
contains(
'Link to overlapping prefix: <a href="csspub/theOnlyThingInTheLibrary.html">renamedLib2.theOnlyThingInTheLibrary</a>'));
});
test('operator [] reference within a class works', () {
expect(
docsAsHtml,
contains(
'<a href="fake/BaseForDocComments/operator_get.html">operator []</a> '));
});
test('operator [] reference outside of a class works', () {
expect(
docsAsHtml,
contains(
'<a href="fake/SpecialList/operator_get.html">SpecialList.operator []</a> '));
}, skip: 'https://github.com/dart-lang/dartdoc/issues/1285');
test('codeifies a class from the SDK', () {
expect(docsAsHtml, contains('<code>String</code>'));
});
test('codeifies a reference to its parameter', () {
expect(docsAsHtml, contains('<code>value</code>'));
});
test('links to a reference to its class', () {
expect(
docsAsHtml,
contains(
'<a href="fake/BaseForDocComments-class.html">BaseForDocComments</a>'));
});
test(
'link to a name in another library in this package, but is not imported into this library, should still be linked',
() {
expect(
docsAsHtml,
contains(
'<a href="anonymous_library/doesStuff.html">doesStuff</a>'));
});
test(
'link to unresolved name in the library in this package still should be linked',
() {
final Class helperClass =
exLibrary.classes.firstWhere((c) => c.name == 'Helper');
expect(helperClass.documentationAsHtml,
contains('<a href="ex/Apple-class.html">Apple</a>'));
expect(helperClass.documentationAsHtml,
contains('<a href="ex/B-class.html">ex.B</a>'));
});
test('link to override method in implementer from base class', () {
final Class helperClass =
baseClassLib.classes.firstWhere((c) => c.name == 'Constraints');
expect(
helperClass.documentationAsHtml,
contains(
'<a href="override_class/BoxConstraints/debugAssertIsValid.html">BoxConstraints.debugAssertIsValid</a>'));
});
test(
'link to a name of a class from an imported library that exports the name',
() {
expect(
docsAsHtml,
contains(
'<a href="two_exports/BaseClass-class.html">BaseClass</a>'));
});
test(
'links to a reference to a top-level const with multiple underscores',
() {
expect(
docsAsHtml,
contains(
'<a href="fake/NAME_WITH_TWO_UNDERSCORES-constant.html">NAME_WITH_TWO_UNDERSCORES</a>'));
});
test('links to a method in this class', () {
expect(
docsAsHtml,
contains(
'<a href="fake/BaseForDocComments/anotherMethod.html">anotherMethod</a>'));
});
test('links to a top-level function in this library', () {
expect(
docsAsHtml,
contains(
'<a class="deprecated" href="fake/topLevelFunction.html">topLevelFunction</a>'));
});
test('links to top-level function from an imported library', () {
expect(
docsAsHtml, contains('<a href="ex/function1.html">function1</a>'));
});
test('links to a class from an imported lib', () {
expect(docsAsHtml, contains('<a href="ex/Apple-class.html">Apple</a>'));
});
test(
'links to a top-level const from same lib (which also has the same name as a const from an imported lib)',
() {
expect(
docsAsHtml,
contains(
'<a href="fake/incorrectDocReference-constant.html">incorrectDocReference</a>'));
});
test('links to a top-level const from an imported lib', () {
expect(
docsAsHtml,
contains(
'<a href="ex/incorrectDocReferenceFromEx-constant.html">incorrectDocReferenceFromEx</a>'));
});
test('links to a top-level variable with a prefix from an imported lib',
() {
expect(
docsAsHtml,
contains(
'<a href="csspub/theOnlyThingInTheLibrary.html">css.theOnlyThingInTheLibrary</a>'));
});
test('links to a name with a single underscore', () {
expect(
docsAsHtml,
contains(
'<a href="fake/NAME_SINGLEUNDERSCORE-constant.html">NAME_SINGLEUNDERSCORE</a>'));
});
test('correctly escapes type parameters in links', () {
expect(
docsAsHtml,
contains(
'<a href="fake/ExtraSpecialList-class.html">ExtraSpecialList&lt;Object&gt;</a>'));
});
test('correctly escapes type parameters in broken links', () {
expect(docsAsHtml,
contains('<code>ThisIsNotHereNoWay&lt;MyType&gt;</code>'));
});
});
test('multi-underscore names in brackets do not become italicized', () {
expect(short.documentation, contains('[NAME_WITH_TWO_UNDERSCORES]'));
expect(
short.documentationAsHtml,
contains(
'<a href="fake/NAME_WITH_TWO_UNDERSCORES-constant.html">NAME_WITH_TWO_UNDERSCORES</a>'));
});
test('still has brackets inside code blocks', () {
expect(topLevelFunction.documentationAsHtml,
contains("['hello from dart']"));
});
test('class without additional docs', () {
expect(specialList.hasExtendedDocumentation, equals(false));
});
test('class with additional docs', () {
expect(Apple.hasExtendedDocumentation, equals(true));
});
test('oneLine doc references in inherited methods should not have brackets',
() {
Method add =
specialList.allInstanceMethods.firstWhere((m) => m.name == 'add');
expect(
add.oneLineDoc,
equals(
'Adds <code>value</code> to the end of this list,\nextending the length by one. <a href="fake/SpecialList/add.html">[...]</a>'));
});
test(
'full documentation references from inherited methods should not have brackets',
() {
Method add =
specialList.allInstanceMethods.firstWhere((m) => m.name == 'add');
expect(
add.documentationAsHtml,
startsWith(
'<p>Adds <code>value</code> to the end of this list,\nextending the length by one.'));
});
test('incorrect doc references are still wrapped in code blocks', () {
expect(incorrectDocReferenceFromEx.documentationAsHtml,
'<p>This should <code>not work</code>.</p>');
});
test('no references', () {
expect(
Apple.documentationAsHtml,
'<p>Sample class <code>String</code></p><pre class="language-dart"> A\n'
' B\n'
'</pre>');
});
test('single ref to class', () {
expect(
B.documentationAsHtml.contains(
'<p>Extends class <a href="ex/Apple-class.html">Apple</a>, use <a href="ex/Apple/Apple.html">new Apple</a> or <a href="ex/Apple/Apple.fromString.html">new Apple.fromString</a></p>'),
isTrue);
});
test('doc ref to class in SDK does not render as link', () {
expect(
thisIsAsync.documentationAsHtml,
equals(
'<p>An async function. It should look like I return a <code>Future</code>.</p>'));
});
test('references are correct in exported libraries', () {
expect(twoExportsLib, isNotNull);
expect(extendedClass, isNotNull);
String resolved = extendedClass.documentationAsHtml;
expect(resolved, isNotNull);
expect(resolved,
contains('<a href="two_exports/BaseClass-class.html">BaseClass</a>'));
expect(resolved,
contains('Linking over to <a href="ex/Apple-class.html">Apple</a>'));
});
test('references to class and constructors', () {
String comment = B.documentationAsHtml;
expect(comment,
contains('Extends class <a href="ex/Apple-class.html">Apple</a>'));
expect(
comment, contains('use <a href="ex/Apple/Apple.html">new Apple</a>'));
expect(
comment,
contains(
'<a href="ex/Apple/Apple.fromString.html">new Apple.fromString</a>'));
});
test('reference to class from another library', () {
String comment = superAwesomeClass.documentationAsHtml;
expect(comment, contains('<a href="ex/Apple-class.html">Apple</a>'));
});
test('reference to method', () {
String comment = foo2.documentationAsHtml;
expect(
comment,
equals(
'<p>link to method from class <a href="ex/Apple/m.html">Apple.m</a></p>'));
});
test(
'code references to privately defined elements in public classes work properly',
() {
Method notAMethodFromPrivateClass = fakeLibrary.allClasses
.firstWhere((Class c) => c.name == 'ReferringClass')
.allPublicInstanceMethods
.firstWhere((Method m) => m.name == 'notAMethodFromPrivateClass');
expect(
notAMethodFromPrivateClass.documentationAsHtml,
contains(
'<a href="fake/InheritingClassOne/aMethod.html">fake.InheritingClassOne.aMethod</a>'));
expect(
notAMethodFromPrivateClass.documentationAsHtml,
contains(
'<a href="fake/InheritingClassTwo/aMethod.html">fake.InheritingClassTwo.aMethod</a>'));
});
test('legacy code blocks render correctly', () {
expect(
testingCodeSyntaxInOneLiners.oneLineDoc,
equals(
'These are code syntaxes: <code>true</code> and <code>false</code>'));
});
test('doc comments to parameters are marked as code', () {
Method localMethod = subForDocComments.instanceMethods
.firstWhere((m) => m.name == 'localMethod');
expect(localMethod.documentationAsHtml, contains('<code>foo</code>'));
expect(localMethod.documentationAsHtml, contains('<code>bar</code>'));
});
test('doc comment inherited from getter', () {
Field getterWithDocs = subForDocComments.instanceProperties
.firstWhere((m) => m.name == 'getterWithDocs');
expect(getterWithDocs.documentationAsHtml,
contains('Some really great topics.'));
});
test(
'a property with no explicit getters and setters does not duplicate docs',
() {
Field powers = superAwesomeClass.instanceProperties
.firstWhere((p) => p.name == 'powers');
Iterable<Match> matches =
RegExp('In the super class').allMatches(powers.documentationAsHtml);
expect(matches, hasLength(1));
});
test('bullet points work in top level variables', () {
expect(bulletDoced.oneLineDoc,
contains('<a href="fake/bulletDoced-constant.html">[...]</a>'));
expect(bulletDoced.documentationAsHtml, contains('<li>'));
});
});
group('Class edge cases', () {
test(
'ExecutableElements from private classes and from public interfaces (#1561)',
() {
Class MIEEMixinWithOverride = fakeLibrary.publicClasses
.firstWhere((c) => c.name == 'MIEEMixinWithOverride');
Operator problematicOperator = MIEEMixinWithOverride.inheritedOperators
.firstWhere((o) => o.name == 'operator []=');
expect(problematicOperator.element.enclosingElement.name,
equals('_MIEEPrivateOverride'));
expect(problematicOperator.canonicalModelElement.enclosingElement.name,
equals('MIEEMixinWithOverride'));
});
});
group('Mixin', () {
Mixin GenericMixin;
Class GenericClass, ModifierClass, TypeInferenceMixedIn;
Field overrideByEverything,
overrideByGenericMixin,
overrideByBoth,
overrideByModifierClass;
setUpAll(() {
Iterable<Class> classes = fakeLibrary.publicClasses;
GenericClass = classes.firstWhere((c) => c.name == 'GenericClass');
ModifierClass = classes.firstWhere((c) => c.name == 'ModifierClass');
GenericMixin =
fakeLibrary.publicMixins.firstWhere((m) => m.name == 'GenericMixin');
TypeInferenceMixedIn =
classes.firstWhere((c) => c.name == 'TypeInferenceMixedIn');
overrideByEverything = TypeInferenceMixedIn.allInstanceFields
.firstWhere((f) => f.name == 'overrideByEverything');
overrideByGenericMixin = TypeInferenceMixedIn.allInstanceFields
.firstWhere((f) => f.name == 'overrideByGenericMixin');
overrideByBoth = TypeInferenceMixedIn.allInstanceFields
.firstWhere((f) => f.name == 'overrideByBoth');
overrideByModifierClass = TypeInferenceMixedIn.allInstanceFields
.firstWhere((f) => f.name == 'overrideByModifierClass');
});
test(('Verify mixin member is available in findRefElementCache'), () {
expect(packageGraph.findRefElementCache['GenericMixin.mixinMember'],
isNotEmpty);
});
test(('Verify inheritance/mixin structure and type inference'), () {
expect(
TypeInferenceMixedIn.mixins
.map<String>((DefinedElementType t) => t.element.name),
orderedEquals(['GenericMixin']));
expect(
TypeInferenceMixedIn.mixins.first.typeArguments
.map<String>((ElementType t) => t.name),
orderedEquals(['int']));
expect(TypeInferenceMixedIn.superChain.length, equals(2));
final ParameterizedElementType firstType =
TypeInferenceMixedIn.superChain.first;
final ParameterizedElementType lastType =
TypeInferenceMixedIn.superChain.last;
expect(firstType.name, equals('ModifierClass'));
expect(firstType.typeArguments.map<String>((ElementType t) => t.name),
orderedEquals(['int']));
expect(lastType.name, equals('GenericClass'));
expect(lastType.typeArguments.map<String>((ElementType t) => t.name),
orderedEquals(['int']));
});
test(('Verify non-overridden members have right canonical classes'), () {
final Field member = TypeInferenceMixedIn.allInstanceFields
.firstWhere((f) => f.name == 'member');
final Field modifierMember = TypeInferenceMixedIn.allInstanceFields
.firstWhere((f) => f.name == 'modifierMember');
final Field mixinMember = TypeInferenceMixedIn.allInstanceFields
.firstWhere((f) => f.name == 'mixinMember');
expect(member.canonicalEnclosingElement, equals(GenericClass));
expect(modifierMember.canonicalEnclosingElement, equals(ModifierClass));
expect(mixinMember.canonicalEnclosingElement, equals(GenericMixin));
});
test(('Verify overrides & documentation inheritance work as intended'), () {
expect(overrideByEverything.canonicalEnclosingElement,
equals(TypeInferenceMixedIn));
expect(overrideByGenericMixin.canonicalEnclosingElement,
equals(GenericMixin));
expect(overrideByBoth.canonicalEnclosingElement, equals(GenericMixin));
expect(overrideByModifierClass.canonicalEnclosingElement,
equals(ModifierClass));
expect(
overrideByEverything.documentationFrom.first,
equals(GenericClass.allInstanceFields
.firstWhere((f) => f.name == 'overrideByEverything')
.getter));
expect(
overrideByGenericMixin.documentationFrom.first,
equals(GenericClass.allInstanceFields
.firstWhere((f) => f.name == 'overrideByGenericMixin')
.getter));
expect(
overrideByBoth.documentationFrom.first,
equals(GenericClass.allInstanceFields
.firstWhere((f) => f.name == 'overrideByBoth')
.getter));
expect(
overrideByModifierClass.documentationFrom.first,
equals(GenericClass.allInstanceFields
.firstWhere((f) => f.name == 'overrideByModifierClass')
.getter));
});
test(('Verify that documentation for mixin applications contains links'),
() {
expect(
overrideByModifierClass.oneLineDoc,
contains(
'<a href=\"fake/ModifierClass-class.html\">ModifierClass</a>'));
expect(
overrideByModifierClass.canonicalModelElement.documentationAsHtml,
contains(
'<a href=\"fake/ModifierClass-class.html\">ModifierClass</a>'));
expect(
overrideByGenericMixin.oneLineDoc,
contains(
'<a href=\"fake/GenericMixin-mixin.html\">GenericMixin</a>'));
expect(
overrideByGenericMixin.canonicalModelElement.documentationAsHtml,
contains(
'<a href=\"fake/GenericMixin-mixin.html\">GenericMixin</a>'));
expect(
overrideByBoth.oneLineDoc,
contains(
'<a href=\"fake/ModifierClass-class.html\">ModifierClass</a>'));
expect(
overrideByBoth.oneLineDoc,
contains(
'<a href=\"fake/GenericMixin-mixin.html\">GenericMixin</a>'));
expect(
overrideByBoth.canonicalModelElement.documentationAsHtml,
contains(
'<a href=\"fake/ModifierClass-class.html\">ModifierClass</a>'));
expect(
overrideByBoth.canonicalModelElement.documentationAsHtml,
contains(
'<a href=\"fake/GenericMixin-mixin.html\">GenericMixin</a>'));
});
});
group('Class', () {
List<Class> classes;
Class Apple, B, Cat, Cool, Dog, F, Dep, SpecialList;
Class ExtendingClass, CatString;
setUpAll(() {
classes = exLibrary.publicClasses.toList();
Apple = classes.firstWhere((c) => c.name == 'Apple');
B = classes.firstWhere((c) => c.name == 'B');
Cat = classes.firstWhere((c) => c.name == 'Cat');
Dog = classes.firstWhere((c) => c.name == 'Dog');
F = classes.firstWhere((c) => c.name == 'F');
Dep = classes.firstWhere((c) => c.name == 'Deprecated');
Cool = classes.firstWhere((c) => c.name == 'Cool');
SpecialList =
fakeLibrary.classes.firstWhere((c) => c.name == 'SpecialList');
ExtendingClass =
twoExportsLib.classes.firstWhere((c) => c.name == 'ExtendingClass');
CatString = exLibrary.classes.firstWhere((c) => c.name == 'CatString');
});
test('has a fully qualified name', () {
expect(Apple.fullyQualifiedName, 'ex.Apple');
});
test('we got the classes we expect', () {
expect(Apple.name, equals('Apple'));
expect(B.name, equals('B'));
expect(Cat.name, equals('Cat'));
expect(Dog.name, equals('Dog'));
});
test('a class with only inherited properties has some properties', () {
expect(CatString.hasInstanceProperties, isFalse);
expect(CatString.instanceProperties, isEmpty);
expect(CatString.hasPublicProperties, isTrue);
expect(CatString.allInstanceFields, isNotEmpty);
});
test('has enclosing element', () {
expect(Apple.enclosingElement.name, equals(exLibrary.name));
});
test('class name with generics', () {
expect(
F.nameWithGenerics,
equals(
'F&lt;<wbr><span class="type-parameter">T extends String</span>&gt;'));
});
test('correctly finds all the classes', () {
expect(classes, hasLength(30));
});
test('abstract', () {
expect(Cat.isAbstract, isTrue);
});
test('supertype', () {
expect(B.hasPublicSuperChainReversed, isTrue);
});
test('mixins', () {
expect(Apple.mixins, hasLength(0));
});
test('mixins private', () {
expect(F.mixins, hasLength(1));
});
test('interfaces', () {
var interfaces = Dog.interfaces;
expect(interfaces, hasLength(2));
expect(interfaces[0].name, 'Cat');
expect(interfaces[1].name, 'E');
});
test('class title has abstract keyword', () {
expect(Cat.fullkind, 'abstract class');
});
test('class title has no abstract keyword', () {
expect(Dog.fullkind, 'class');
});
test('get constructors', () {
expect(Apple.publicConstructors, hasLength(2));
});
test('get static fields', () {
expect(Apple.publicStaticProperties, hasLength(1));
});
test('constructors have source', () {
Constructor ctor = Dog.constructors.first;
expect(ctor.sourceCode, isNotEmpty);
});
test('get constants', () {
expect(Apple.publicConstants, hasLength(1));
expect(Apple.publicConstants.first.kind, equals('constant'));
});
test('get instance fields', () {
expect(Apple.publicInstanceProperties, hasLength(3));
expect(Apple.publicInstanceProperties.first.kind, equals('property'));
});
test('get inherited properties, including properties of Object', () {
expect(B.publicInheritedProperties, hasLength(4));
});
test('get methods', () {
expect(Dog.publicInstanceMethods, hasLength(22));
});
test('get operators', () {
expect(Dog.publicOperators, hasLength(1));
expect(Dog.publicOperators.first.name, 'operator ==');
});
test('inherited methods, including from Object ', () {
expect(B.publicInheritedMethods, hasLength(7));
expect(B.hasPublicInheritedMethods, isTrue);
});
test('all instance methods', () {
expect(B.allPublicInstanceMethods, isNotEmpty);
expect(
B.allPublicInstanceMethods.length,
equals(B.publicInstanceMethods.length +
B.publicInheritedMethods.length));
});
test('inherited methods exist', () {
expect(B.inheritedMethods.firstWhere((x) => x.name == 'printMsg'),
isNotNull);
expect(B.inheritedMethods.firstWhere((x) => x.name == 'isGreaterThan'),
isNotNull);
});
test('exported class should have hrefs from the current library', () {
expect(Dep.href, equals('ex/Deprecated-class.html'));
expect(
Dep.instanceMethods[0].href, equals('ex/Deprecated/toString.html'));
expect(
Dep.instanceProperties[0].href, equals('ex/Deprecated/expires.html'));
});
test('exported class should have linkedReturnType for the current library',
() {
Method returnCool = Cool.instanceMethods
.firstWhere((m) => m.name == 'returnCool', orElse: () => null);
expect(returnCool, isNotNull);
expect(returnCool.linkedReturnType,
equals('<a href="fake/Cool-class.html">Cool</a>'));
});
test('F has a single instance method', () {
expect(F.publicInstanceMethods, hasLength(1));
expect(
F.publicInstanceMethods.first.name, equals('methodWithGenericParam'));
});
test('F has many inherited methods', () {
expect(
F.publicInheritedMethods.map((im) => im.name),
containsAll([
'abstractMethod',
'foo',
'getAnotherClassD',
'getClassA',
'noSuchMethod',
'test',
'testGeneric',
'testGenericMethod',
'testMethod',
'toString',
'withAnimation',
'withAnimationInline',
'withAnimationOutOfOrder',
'withAnimationInOneLineDoc',
'withDeprecatedAnimation',
'withMacro',
'withMacro2',
'withNamedAnimation',
'withPrivateMacro',
'withQuotedNamedAnimation',
'withUndefinedMacro',
'withYouTubeInline',
'withYouTubeInOneLineDoc',
'withYouTubeWatchUrl',
]));
});
test('F has zero instance properties', () {
expect(F.publicInstanceProperties, hasLength(0));
});
test('F has a few inherited properties', () {
expect(F.publicInheritedProperties, hasLength(10));
expect(
F.publicInheritedProperties.map((ip) => ip.name),
containsAll([
'aFinalField',
'aGetterReturningRandomThings',
'aProtectedFinalField',
'deprecatedField',
'deprecatedGetter',
'deprecatedSetter',
'hashCode',
'isImplemented',
'name',
'runtimeType'
]));
});
test('SpecialList has zero instance methods', () {
expect(SpecialList.publicInstanceMethods, hasLength(0));
});
test('SpecialList has many inherited methods', () {
expect(SpecialList.publicInheritedMethods, hasLength(49));
expect(SpecialList.publicInheritedMethods.first.name, equals('add'));
expect(SpecialList.publicInheritedMethods.toList()[1].name,
equals('addAll'));
});
test('ExtendingClass is in the right library', () {
expect(ExtendingClass.library.name, equals('two_exports'));
});
// because both the sub and super classes, though from different libraries,
// are exported out through one library
test('ExtendingClass has a super class that is also in the same library',
() {
// The real implementation of BaseClass is private, but it is exported.
expect(ExtendingClass.superChain.first.name, equals('BaseClass'));
expect(
ExtendingClass.superChain.first.element.isCanonical, equals(false));
expect(ExtendingClass.superChain.first.element.isPublic, equals(false));
// And it should still show up in the publicSuperChain, because it is
// exported.
expect(ExtendingClass.publicSuperChain.first.name, equals('BaseClass'));
expect(
ExtendingClass.publicSuperChain.first.element.canonicalLibrary.name,
equals('two_exports'));
});
test(
"ExtendingClass's super class has a library that is not in two_exports",
() {
expect(
ExtendingClass.superChain.last.name, equals('WithGetterAndSetter'));
expect(
ExtendingClass.superChain.last.element.library.name, equals('fake'));
});
});
group('Extension', () {
Extension ext, fancyList;
Method s;
List<Extension> extensions;
setUpAll(() {
ext = exLibrary.extensions.firstWhere((e) => e.name == 'AppleExtension');
fancyList = exLibrary.extensions.firstWhere((e) => e.name == 'FancyList');
extensions = exLibrary.publicExtensions.toList();
});
test('has a fully qualified name', () {
expect(ext.fullyQualifiedName, 'ex.AppleExtension');
});
test('has enclosing element', () {
expect(ext.enclosingElement.name, equals(exLibrary.name));
});
test('member method has href', () {
s = ext.instanceMethods.firstWhere((m) => m.name == 's');
expect(s.href, 'ex/AppleExtension/s.html');
});
test('has extended type', () {
expect(ext.extendedType.name, equals("Apple"));
});
test('extension name with generics', () {
expect(
fancyList.nameWithGenerics,
equals(
'FancyList&lt;<wbr><span class="type-parameter">Z</span>&gt;'));
});
test('get methods', () {
expect(fancyList.allPublicInstanceMethods, hasLength(1));
});
test('get operators', () {
expect(fancyList.allPublicOperators, hasLength(1));
});
test('get static methods', () {
expect(fancyList.publicStaticMethods, hasLength(1));
});
test('get properties', () {
expect(fancyList.publicInstanceProperties, hasLength(1));
});
test('get contants', () {
expect(fancyList.publicConstants, hasLength(0));
});
test('correctly finds all the extensions', () {
expect(exLibrary.extensions, hasLength(7));
});
test('correctly finds all the public extensions', () {
expect(extensions, hasLength(5));
});
});
group('Enum', () {
Enum animal;
setUpAll(() {
animal = exLibrary.enums.firstWhere((e) => e.name == 'Animal');
/// Trigger code reference resolution
animal.documentationAsHtml;
});
test('has a fully qualified name', () {
expect(animal.fullyQualifiedName, 'ex.Animal');
});
test('has enclosing element', () {
expect(animal.enclosingElement.name, equals(exLibrary.name));
});
test('has correct number of constants', () {
expect(animal.constants, hasLength(4));
});
test("has a (synthetic) values constant", () {
var values = animal.constants.firstWhere((f) => f.name == 'values');
expect(values, isNotNull);
expect(
values.constantValue,
equals(
'const List&lt;<wbr><span class="type-parameter">Animal</span>&gt;'));
expect(values.documentation, startsWith('A constant List'));
});
test('has a constant that does not link anywhere', () {
var dog = animal.constants.firstWhere((f) => f.name == 'DOG');
expect(dog.linkedName, equals('DOG'));
expect(dog.isConst, isTrue);
expect(dog.constantValue, equals('const Animal(1)'));
});
test('constants have correct indicies', () {
String valueByName(var name) {
return animal.constants.firstWhere((f) => f.name == name).constantValue;
}
expect(valueByName('CAT'), equals('const Animal(0)'));
expect(valueByName('DOG'), equals('const Animal(1)'));
expect(valueByName('HORSE'), equals('const Animal(2)'));
});
test('has a single `index` property', () {
expect(animal.instanceProperties, hasLength(1));
expect(animal.instanceProperties.first, isNotNull);
expect(animal.instanceProperties.first.name, equals('index'));
});
test('has a single `index` property that is not linked', () {
expect(animal.instanceProperties.first.linkedName, equals('index'));
});
});
group('Function', () {
ModelFunction f1;
ModelFunction genericFunction;
ModelFunction paramOfFutureOrNull;
ModelFunction thisIsAsync;
ModelFunction thisIsFutureOr;
ModelFunction thisIsFutureOrNull;
ModelFunction thisIsFutureOrT;
ModelFunction topLevelFunction;
ModelFunction typeParamOfFutureOr;
setUpAll(() {
f1 = exLibrary.functions.first;
genericFunction =
exLibrary.functions.firstWhere((f) => f.name == 'genericFunction');
paramOfFutureOrNull = fakeLibrary.functions
.firstWhere((f) => f.name == 'paramOfFutureOrNull');
thisIsAsync =
fakeLibrary.functions.firstWhere((f) => f.name == 'thisIsAsync');
thisIsFutureOr =
fakeLibrary.functions.firstWhere((f) => f.name == 'thisIsFutureOr');
thisIsFutureOrNull = fakeLibrary.functions
.firstWhere((f) => f.name == 'thisIsFutureOrNull');
thisIsFutureOrT =
fakeLibrary.functions.firstWhere((f) => f.name == 'thisIsFutureOrT');
topLevelFunction =
fakeLibrary.functions.firstWhere((f) => f.name == 'topLevelFunction');
typeParamOfFutureOr = fakeLibrary.functions
.firstWhere((f) => f.name == 'typeParamOfFutureOr');
});
test('has a fully qualified name', () {
expect(thisIsAsync.fullyQualifiedName, 'fake.thisIsAsync');
});
test('has enclosing element', () {
expect(f1.enclosingElement.name, equals(exLibrary.name));
});
test('name is function1', () {
expect(f1.name, 'function1');
});
test('local element', () {
expect(f1.isLocalElement, true);
});
test('is executable', () {
expect(f1.isExecutable, true);
});
test('is static', () {
expect(f1.isStatic, true);
});
test('handles dynamic parameters correctly', () {
expect(f1.linkedParams(), contains('lastParam'));
});
test('async function', () {
expect(thisIsAsync.isAsynchronous, isTrue);
expect(thisIsAsync.linkedReturnType, equals('Future'));
expect(
thisIsAsync.documentation,
equals(
'An async function. It should look like I return a [Future].'));
expect(
thisIsAsync.documentationAsHtml,
equals(
'<p>An async function. It should look like I return a <code>Future</code>.</p>'));
});
test('function returning FutureOr', () {
expect(thisIsFutureOr.isAsynchronous, isFalse);
expect(thisIsFutureOr.linkedReturnType, equals('FutureOr'));
});
test('function returning FutureOr<Null>', () {
expect(thisIsFutureOrNull.isAsynchronous, isFalse);
expect(
thisIsFutureOrNull.linkedReturnType,
equals(
'FutureOr<span class="signature">&lt;<wbr><span class="type-parameter">Null</span>&gt;</span>'));
});
test('function returning FutureOr<T>', () {
expect(thisIsFutureOrNull.isAsynchronous, isFalse);
expect(
thisIsFutureOrT.linkedReturnType,
equals(
'FutureOr<span class="signature">&lt;<wbr><span class="type-parameter">T</span>&gt;</span>'));
});
test('function with a parameter having type FutureOr<Null>', () {
expect(
paramOfFutureOrNull.linkedParams(),
equals(
'<span class="parameter" id="paramOfFutureOrNull-param-future"><span class="type-annotation">FutureOr<span class="signature">&lt;<wbr><span class="type-parameter">Null</span>&gt;</span></span> <span class="parameter-name">future</span></span>'));
});
test('function with a bound type to FutureOr', () {
expect(
typeParamOfFutureOr.linkedGenericParameters,
equals(
'<span class="signature">&lt;<wbr><span class="type-parameter">T extends FutureOr<span class="signature">&lt;<wbr><span class="type-parameter">List</span>&gt;</span></span>&gt;</span>'));
});
test('docs do not lose brackets in code blocks', () {
expect(topLevelFunction.documentation, contains("['hello from dart']"));
});
test('has source code', () {
expect(topLevelFunction.sourceCode, startsWith('@deprecated'));
expect(topLevelFunction.sourceCode, endsWith('''
String topLevelFunction(int param1, bool param2, Cool coolBeans,
[double optionalPositional = 0.0]) {
return null;
}'''));
});
test('typedef params have proper signature', () {
ModelFunction function =
fakeLibrary.functions.firstWhere((f) => f.name == 'addCallback');
String params = function.linkedParams();
expect(
params,
'<span class="parameter" id="addCallback-param-callback">'
'<span class="type-annotation"><a href="fake/VoidCallback.html">VoidCallback</a></span> '
'<span class="parameter-name">callback</span></span>');
function =
fakeLibrary.functions.firstWhere((f) => f.name == 'addCallback2');
params = function.linkedParams();
expect(
params,
'<span class="parameter" id="addCallback2-param-callback">'
'<span class="type-annotation"><a href="fake/Callback2.html">Callback2</a></span> '
'<span class="parameter-name">callback</span></span>');
});
test('supports generic methods', () {
expect(genericFunction.nameWithGenerics,
'genericFunction&lt;<wbr><span class="type-parameter">T</span>&gt;');
});
});
group('Type expansion', () {
Class TemplatedInterface, ClassWithUnusualProperties;
setUpAll(() {
TemplatedInterface =
exLibrary.classes.singleWhere((c) => c.name == 'TemplatedInterface');
ClassWithUnusualProperties = fakeLibrary.classes
.singleWhere((c) => c.name == 'ClassWithUnusualProperties');
});
test('setter that takes a function is correctly displayed', () {
Field explicitSetter = ClassWithUnusualProperties.instanceProperties
.singleWhere((f) => f.name == 'explicitSetter');
// TODO(jcollins-g): really, these shouldn't be called "parameters" in
// the span class.
expect(explicitSetter.linkedReturnType,
'<span class="parameter" id="explicitSetter=-param-f"><span class="type-annotation">dynamic</span> <span class="parameter-name">Function</span>(<span class="parameter" id="f-param-bar"><span class="type-annotation">int</span>, </span> <span class="parameter" id="f-param-baz"><span class="type-annotation"><a href="fake/Cool-class.html">Cool</a></span>, </span> <span class="parameter" id="f-param-macTruck"><span class="type-annotation">List<span class="signature">&lt;<wbr><span class="type-parameter">int</span>&gt;</span></span></span>)</span>');
});
test('parameterized type from field is correctly displayed', () {
Field aField = TemplatedInterface.instanceProperties
.singleWhere((f) => f.name == 'aField');
expect(aField.linkedReturnType,
'<a href="ex/AnotherParameterizedClass-class.html">AnotherParameterizedClass</a><span class="signature">&lt;<wbr><span class="type-parameter">Stream<span class="signature">&lt;<wbr><span class="type-parameter">List<span class="signature">&lt;<wbr><span class="type-parameter">int</span>&gt;</span></span>&gt;</span></span>&gt;</span>');
});
test('parameterized type from inherited field is correctly displayed', () {
Field aInheritedField = TemplatedInterface.inheritedProperties
.singleWhere((f) => f.name == 'aInheritedField');
expect(aInheritedField.linkedReturnType,
'<a href="ex/AnotherParameterizedClass-class.html">AnotherParameterizedClass</a><span class="signature">&lt;<wbr><span class="type-parameter">List<span class="signature">&lt;<wbr><span class="type-parameter">int</span>&gt;</span></span>&gt;</span>');
});
test(
'parameterized type for return value from explicit getter is correctly displayed',
() {
Accessor aGetter = TemplatedInterface.instanceProperties
.singleWhere((f) => f.name == 'aGetter')
.getter;
expect(aGetter.linkedReturnType,
'<a href="ex/AnotherParameterizedClass-class.html">AnotherParameterizedClass</a><span class="signature">&lt;<wbr><span class="type-parameter">Map<span class="signature">&lt;<wbr><span class="type-parameter">A</span>, <span class="type-parameter">List<span class="signature">&lt;<wbr><span class="type-parameter">String</span>&gt;</span></span>&gt;</span></span>&gt;</span>');
});
test(
'parameterized type for return value from inherited explicit getter is correctly displayed',
() {
Accessor aInheritedGetter = TemplatedInterface.inheritedProperties
.singleWhere((f) => f.name == 'aInheritedGetter')
.getter;
expect(aInheritedGetter.linkedReturnType,
'<a href="ex/AnotherParameterizedClass-class.html">AnotherParameterizedClass</a><span class="signature">&lt;<wbr><span class="type-parameter">List<span class="signature">&lt;<wbr><span class="type-parameter">int</span>&gt;</span></span>&gt;</span>');
});
test(
'parameterized type for return value from inherited explicit setter is correctly displayed',
() {
Accessor aInheritedSetter = TemplatedInterface.inheritedProperties
.singleWhere((f) => f.name == 'aInheritedSetter')
.setter;
expect(aInheritedSetter.allParameters.first.modelType.linkedName,
'<a href="ex/AnotherParameterizedClass-class.html">AnotherParameterizedClass</a><span class="signature">&lt;<wbr><span class="type-parameter">List<span class="signature">&lt;<wbr><span class="type-parameter">int</span>&gt;</span></span>&gt;</span>');
// TODO(jcollins-g): really, these shouldn't be called "parameters" in
// the span class.
expect(aInheritedSetter.enclosingCombo.linkedReturnType,
'<span class="parameter" id="aInheritedSetter=-param-thingToSet"><span class="type-annotation"><a href="ex/AnotherParameterizedClass-class.html">AnotherParameterizedClass</a><span class="signature">&lt;<wbr><span class="type-parameter">List<span class="signature">&lt;<wbr><span class="type-parameter">int</span>&gt;</span></span>&gt;</span></span></span>');
});
test(
'parameterized type for return value from method is correctly displayed',
() {
Method aMethodInterface = TemplatedInterface.allInstanceMethods
.singleWhere((m) => m.name == 'aMethodInterface');
expect(aMethodInterface.linkedReturnType,
'<a href="ex/AnotherParameterizedClass-class.html">AnotherParameterizedClass</a><span class="signature">&lt;<wbr><span class="type-parameter">List<span class="signature">&lt;<wbr><span class="type-parameter">int</span>&gt;</span></span>&gt;</span>');
});
test(
'parameterized type for return value from inherited method is correctly displayed',
() {
Method aInheritedMethod = TemplatedInterface.allInstanceMethods
.singleWhere((m) => m.name == 'aInheritedMethod');
expect(aInheritedMethod.linkedReturnType,
'<a href="ex/AnotherParameterizedClass-class.html">AnotherParameterizedClass</a><span class="signature">&lt;<wbr><span class="type-parameter">List<span class="signature">&lt;<wbr><span class="type-parameter">int</span>&gt;</span></span>&gt;</span>');
});
test(
'parameterized type for return value containing a parameterized typedef is correctly displayed',
() {
Method aTypedefReturningMethodInterface = TemplatedInterface
.allInstanceMethods
.singleWhere((m) => m.name == 'aTypedefReturningMethodInterface');
expect(aTypedefReturningMethodInterface.linkedReturnType,
'<a href="ex/ParameterizedTypedef.html">ParameterizedTypedef</a><span class="signature">&lt;<wbr><span class="type-parameter">List<span class="signature">&lt;<wbr><span class="type-parameter">String</span>&gt;</span></span>&gt;</span>');
});
test(
'parameterized type for return value containing a parameterized typedef from inherited method is correctly displayed',
() {
Method aInheritedTypedefReturningMethod = TemplatedInterface
.allInstanceMethods
.singleWhere((m) => m.name == 'aInheritedTypedefReturningMethod');
expect(aInheritedTypedefReturningMethod.linkedReturnType,
'<a href="ex/ParameterizedTypedef.html">ParameterizedTypedef</a><span class="signature">&lt;<wbr><span class="type-parameter">List<span class="signature">&lt;<wbr><span class="type-parameter">int</span>&gt;</span></span>&gt;</span>');
});
test('parameterized types for inherited operator is correctly displayed',
() {
Operator aInheritedAdditionOperator = TemplatedInterface
.inheritedOperators
.singleWhere((m) => m.name == 'operator +');
expect(aInheritedAdditionOperator.linkedReturnType,
'<a href="ex/ParameterizedClass-class.html">ParameterizedClass</a><span class="signature">&lt;<wbr><span class="type-parameter">List<span class="signature">&lt;<wbr><span class="type-parameter">int</span>&gt;</span></span>&gt;</span>');
expect(aInheritedAdditionOperator.linkedParams(),
'<span class="parameter" id="+-param-other"><span class="type-annotation"><a href="ex/ParameterizedClass-class.html">ParameterizedClass</a><span class="signature">&lt;<wbr><span class="type-parameter">List<span class="signature">&lt;<wbr><span class="type-parameter">int</span>&gt;</span></span>&gt;</span></span> <span class="parameter-name">other</span></span>');
});
test('', () {});
});
group('Method', () {
Class classB,
klass,
HasGenerics,
Cat,
CatString,
TypedFunctionsWithoutTypedefs;
Method m1, isGreaterThan, m4, m5, m6, m7, convertToMap, abstractMethod;
Method inheritedClear, testGeneric, testGenericMethod;
Method getAFunctionReturningVoid, getAFunctionReturningBool;
setUpAll(() {
klass = exLibrary.classes.singleWhere((c) => c.name == 'Klass');
classB = exLibrary.classes.singleWhere((c) => c.name == 'B');
HasGenerics =
fakeLibrary.classes.singleWhere((c) => c.name == 'HasGenerics');
CatString = exLibrary.classes.singleWhere((c) => c.name == 'CatString');
Cat = exLibrary.classes.singleWhere((c) => c.name == 'Cat');
inheritedClear =
CatString.inheritedMethods.singleWhere((m) => m.name == 'clear');
m1 = classB.instanceMethods.singleWhere((m) => m.name == 'm1');
isGreaterThan = exLibrary.classes
.singleWhere((c) => c.name == 'Apple')
.instanceMethods
.singleWhere((m) => m.name == 'isGreaterThan');
m4 = classB.instanceMethods.singleWhere((m) => m.name == 'writeMsg');
m5 = klass.instanceMethods.singleWhere((m) => m.name == 'another');
m6 = klass.instanceMethods.singleWhere((m) => m.name == 'toString');
m7 = classB.instanceMethods.singleWhere((m) => m.name == 'doNothing');
abstractMethod =
Cat.instanceMethods.singleWhere((m) => m.name == 'abstractMethod');
testGeneric = exLibrary.classes
.singleWhere((c) => c.name == 'Dog')
.instanceMethods
.singleWhere((m) => m.name == 'testGeneric');
testGenericMethod = exLibrary.classes
.singleWhere((c) => c.name == 'Dog')
.instanceMethods
.singleWhere((m) => m.name == 'testGenericMethod');
convertToMap = HasGenerics.instanceMethods
.singleWhere((m) => m.name == 'convertToMap');
TypedFunctionsWithoutTypedefs = exLibrary.classes
.singleWhere((c) => c.name == 'TypedFunctionsWithoutTypedefs');
getAFunctionReturningVoid = TypedFunctionsWithoutTypedefs.instanceMethods
.singleWhere((m) => m.name == 'getAFunctionReturningVoid');
getAFunctionReturningBool = TypedFunctionsWithoutTypedefs.instanceMethods
.singleWhere((m) => m.name == 'getAFunctionReturningBool');
});
test('verify parameter types are correctly displayed', () {
expect(
getAFunctionReturningVoid.linkedReturnType,
equals(
'void Function<span class="signature">(<span class="parameter" id="getAFunctionReturningVoid-param-"><span class="type-annotation">T1</span>, </span> <span class="parameter" id="getAFunctionReturningVoid-param-"><span class="type-annotation">T2</span></span>)</span>'));
});
test(
'verify type parameters to anonymous functions are distinct from normal parameters and instantiated type parameters from method, displayed correctly',
() {
expect(
getAFunctionReturningBool.linkedReturnType,
equals(
'bool Function<span class="signature">&lt;<wbr><span class="type-parameter">T4</span>&gt;</span><span class="signature">(<span class="parameter" id="getAFunctionReturningBool-param-"><span class="type-annotation">String</span>, </span> <span class="parameter" id="getAFunctionReturningBool-param-"><span class="type-annotation">T1</span>, </span> <span class="parameter" id="getAFunctionReturningBool-param-"><span class="type-annotation">T4</span></span>)</span>'));
});
test('has a fully qualified name', () {
expect(m1.fullyQualifiedName, 'ex.B.m1');
});
test('has abstract kind', () {
expect(abstractMethod.fullkind, 'abstract method');
});
test("returns correct overriddenDepth", () {
final bAbstractMethod = classB.allInstanceMethods
.firstWhere((m) => m.name == "abstractMethod");
expect(abstractMethod.overriddenDepth, equals(0));
expect(bAbstractMethod.overriddenDepth, equals(1));
});
test(
'an inherited method has class as the enclosing class, when superclass not in package',
() {
expect(inheritedClear.enclosingElement.name, equals('CatString'));
});
test(
'inherited method has the current library, when superclass library not in package',
() {
expect(inheritedClear.library.name, equals('ex'));
});
test(
'an inherited method from the core SDK has a href relative to the package class',
() {
expect(inheritedClear.href, equals('ex/CatString/clear.html'));
});
test(
'an inherited method has linked to enclosed class name when superclass not in package',
() {
expect(inheritedClear.linkedName,
equals('<a href="ex/CatString/clear.html">clear</a>'));
});
test('has enclosing element', () {
expect(m1.enclosingElement.name, equals(classB.name));
});
test('overriden method', () {
expect(m1.overriddenElement.runtimeType.toString(), 'Method');
});
test('method documentation', () {
expect(m1.documentation,
equals('This is a method.\n\n new Apple().m1();'));
});