Add tests and patch up remaining dartdoc_options.yaml options (#1790)

* dartdoc options testing

* Fully support all declared options

* dartfmt

* scorer tuning isn't officially supported

* Adjust whitespace
diff --git a/README.md b/README.md
index 886352f..4837c12 100644
--- a/README.md
+++ b/README.md
@@ -100,6 +100,8 @@
 Creating a file named dartdoc_options.yaml at the top of your package can change how Dartdoc
 generates docs.  
 
+An example:
+
 ```yaml
 dartdoc:
   categories: 
@@ -110,8 +112,11 @@
       markdown: doc/Second.md
       name: Great
   categoryOrder: ["First Category", "Second Category"]
+  examplePathPrefix: 'subdir/with/examples'
+  includeExternal: ['bin/unusually_located_library.dart']
   linkTo:
     url: "https://my.dartdocumentationsite.org/dev/%v%"
+  showUndocumentedCategories: true
 ```
 
 Unrecognized options will be ignored.  Supported options:
@@ -122,9 +127,16 @@
     defined in dartdoc_options.yaml, those declared categories in the source code will be invisible.
   * **categoryOrder**:  Specify the order of topics for display in the sidebar and
     the package page.
+  * **examplePathPrefix**: Specify the location of the example directory for resolving `@example`
+    directives.
   * **exclude**:  Specify a list of library names to avoid generating docs for,
     overriding any specified in include.
+  * **favicon**:  A path to a favicon for the generated docs.
+  * **footer**: A list of paths to footer files containing HTML text.
+  * **footerText**: A list of paths to text files for optional text next to the package name and version
+  * **header**:  A list of paths to header files containing HTML text.
   * **include**:  Specify a list of library names to generate docs for, ignoring all others.
+  * **includeExternal**:  Specify a list of library filenames to add to the list of documented libraries.
   * **linkTo**:  For other packages depending on this one, if this map is defined those packages
     will use the settings here to control how hyperlinks to the package are generated.
     This will override the default for packages hosted on pub.dartlang.org.
@@ -139,20 +151,15 @@
       * `%n%`: The name of this package, as defined in pubspec.yaml.
       * `%v%`: The version of this package as defined in pubspec.yaml.
 
-The following are experimental options whose semantics are in flux and may be buggy.  If you
-use one, please keep a close eye on the changing semantics.  In general, paths are relative
-to the directory the dartdoc_options.yaml the option is defined in and should be specified
-as POSIX paths.  Dartdoc will convert POSIX paths automatically on Windows.
+In general, paths are relative to the directory the dartdoc_options.yaml the option is defined in
+and should be specified as POSIX paths.  Dartdoc will convert POSIX paths automatically on Windows.
 
-  * **ambiguousReexportScorerMinConfidence**:  The ambiguous reexport scorer will emit a warning if
-  it is not at least this confident.  Default: 0.1
-  * **examplePathPrefix**:  Specify the prefix for the example paths, defaulting to the project root.
-  * **favicon**:  A path to a favicon for the generated docs.
-  * **footer**: A list of paths to footer files containing HTML text.
-  * **footerText**: A list of paths to text files for optional text next to the package name and version
-  * **header**:  A list of paths to header files containing HTML text.
-  * **includeExternal**:  Specify a list of library filenames to add to the list of documented libraries.
- 
+Unsupported and experimental options:
+
+   * **ambiguousReexportScorerMinConfidence**:  The ambiguous reexport scorer will emit a warning if
+     it is not at least this confident.  Adjusting this may be necessary for some complex
+     packages but most of the time, the default is OK.  Default: 0.1
+
 ### Categories
 
 You can tag libraries or top level classes, functions, and variables in their documentation with
diff --git a/lib/src/model.dart b/lib/src/model.dart
index c246469..93df5c3 100644
--- a/lib/src/model.dart
+++ b/lib/src/model.dart
@@ -6393,6 +6393,7 @@
       /// source list.
       if (originalSources == null) originalSources = new Set()..addAll(sources);
       files.addAll(driver.knownFiles);
+      files.addAll(_includeExternalsFrom(driver.knownFiles));
       current = _packageMetasForFiles(files);
       // To get canonicalization correct for non-locally documented packages
       // (so we can generate the right hyperlinks), it's vital that we
@@ -6463,6 +6464,22 @@
     }
   }
 
+  /// Calculate includeExternals based on a list of files.  Assumes each
+  /// file might be part of a [DartdocOptionContext], and loads those
+  /// objects to find any [DartdocOptionContext.includeExternal] configurations
+  /// therein.
+  Iterable<String> _includeExternalsFrom(Iterable<String> files) {
+    Set<String> includeExternalsFound = new Set();
+    for (String file in files) {
+      DartdocOptionContext fileContext =
+          new DartdocOptionContext.fromContext(config, new File(file));
+      if (fileContext.includeExternal != null) {
+        includeExternalsFound.addAll(fileContext.includeExternal);
+      }
+    }
+    return includeExternalsFound;
+  }
+
   Set<String> get getFiles {
     Set<String> files = new Set();
     files.addAll(config.topLevelPackageMeta.isSdk
@@ -6478,11 +6495,8 @@
         files.add(source.fullName);
       });
     }
-    // Use the includeExternals.
-    for (String fullName in driver.knownFiles) {
-      if (config.includeExternal.any((string) => fullName.endsWith(string)))
-        files.add(fullName);
-    }
+
+    files.addAll(_includeExternalsFrom(files));
     return new Set.from(files.map((s) => new File(s).absolute.path));
   }
 
diff --git a/test/dartdoc_test.dart b/test/dartdoc_test.dart
index dab6e7d..752d227 100644
--- a/test/dartdoc_test.dart
+++ b/test/dartdoc_test.dart
@@ -35,6 +35,81 @@
           argv..addAll(['--input', packageRoot.path])..addAll(outputParam)));
     }
 
+    group('Option handling', () {
+      Dartdoc dartdoc;
+      DartdocResults results;
+      PackageGraph p;
+
+      setUp(() async {
+        dartdoc = await buildDartdoc([], testPackageOptions);
+        results = await dartdoc.generateDocsBase();
+        p = results.packageGraph;
+      });
+
+      test('generator parameters', () async {
+        File favicon = new File(
+            pathLib.joinAll([tempDir.path, 'static-assets', 'favicon.png']));
+        File index = new File(pathLib.joinAll([tempDir.path, 'index.html']));
+        expect(favicon.readAsStringSync(),
+            contains('Not really a png, but a test file'));
+        String indexString = index.readAsStringSync();
+        expect(indexString, contains('Footer things'));
+        expect(indexString, contains('footer.txt data'));
+        expect(indexString, contains('HTML header file'));
+      });
+
+      test('examplePathPrefix', () async {
+        Class UseAnExampleHere = p.allCanonicalModelElements
+            .firstWhere((ModelElement c) => c.name == 'UseAnExampleHere');
+        expect(
+            UseAnExampleHere.documentationAsHtml,
+            contains(
+                'An example of an example in an unusual example location.'));
+      });
+
+      test('includeExternal and showUndocumentedCategories', () async {
+        Class Something = p.allCanonicalModelElements
+            .firstWhere((ModelElement c) => c.name == 'Something');
+        expect(Something.isPublic, isTrue);
+        expect(Something.displayedCategories, isNotEmpty);
+      });
+    });
+
+    group('Option handling with cross-linking', () {
+      DartdocResults results;
+      Package testPackageOptions;
+
+      setUp(() async {
+        results = await (await buildDartdoc(
+                ['--link-to-remote'], testPackageOptionsImporter))
+            .generateDocsBase();
+        testPackageOptions = results.packageGraph.packages
+            .firstWhere((Package p) => p.name == 'test_package_options');
+      });
+
+      test('linkToUrl', () async {
+        Library main = testPackageOptions.allLibraries
+            .firstWhere((Library l) => l.name == 'main');
+        Class UseAnExampleHere = main.allClasses
+            .firstWhere((Class c) => c.name == 'UseAnExampleHere');
+        expect(testPackageOptions.documentedWhere,
+            equals(DocumentLocation.remote));
+        expect(
+            UseAnExampleHere.href,
+            equals(
+                'https://nonexistingsuperpackage.topdomain/test_package_options/0.0.1/main/UseAnExampleHere-class.html'));
+      });
+
+      test('includeExternal works via remote', () async {
+        Library unusualLibrary = testPackageOptions.allLibraries
+            .firstWhere((Library l) => l.name == 'unusualLibrary');
+        expect(
+            unusualLibrary.allClasses
+                .firstWhere((Class c) => c.name == 'Something'),
+            isNotNull);
+      });
+    });
+
     test('with broken reexport chain', () async {
       Dartdoc dartdoc = await buildDartdoc([], testPackageImportExportError);
       DartdocResults results = await dartdoc.generateDocsBase();
diff --git a/test/src/utils.dart b/test/src/utils.dart
index e177d72..ed63e28 100644
--- a/test/src/utils.dart
+++ b/test/src/utils.dart
@@ -31,6 +31,10 @@
     new Directory('testing/test_package_include_exclude');
 final Directory testPackageImportExportError =
     new Directory('testing/test_package_import_export_error');
+final Directory testPackageOptions =
+    new Directory('testing/test_package_options');
+final Directory testPackageOptionsImporter =
+    new Directory('testing/test_package_options_importer');
 
 /// Convenience factory to build a [DartdocGeneratorOptionContext] and associate
 /// it with a [DartdocOptionSet] based on the current working directory and/or
diff --git a/testing/test_package_options/anicon.png b/testing/test_package_options/anicon.png
new file mode 100644
index 0000000..2575d43
--- /dev/null
+++ b/testing/test_package_options/anicon.png
@@ -0,0 +1 @@
+Not really a png, but a test file
diff --git a/testing/test_package_options/bin/unusual_library.dart b/testing/test_package_options/bin/unusual_library.dart
new file mode 100644
index 0000000..08dc13e
--- /dev/null
+++ b/testing/test_package_options/bin/unusual_library.dart
@@ -0,0 +1,4 @@
+/// A library placed in an unusual place.
+library unusualLibrary;
+
+class Something {}
diff --git a/testing/test_package_options/dartdoc_options.yaml b/testing/test_package_options/dartdoc_options.yaml
new file mode 100644
index 0000000..84a0f1d
--- /dev/null
+++ b/testing/test_package_options/dartdoc_options.yaml
@@ -0,0 +1,10 @@
+dartdoc:
+  examplePathPrefix: 'package_examples'
+  favicon: 'anicon.png'
+  footer: ['extras/footer-things.html']
+  footerText: ['extras/footer.txt']
+  header: ['extras/header.html']
+  includeExternal: ['bin/unusual_library.dart']
+  linkTo:
+    url: 'https://nonexistingsuperpackage.topdomain/%n%/%v%'
+  showUndocumentedCategories: true
diff --git a/testing/test_package_options/extras/footer-things.html b/testing/test_package_options/extras/footer-things.html
new file mode 100644
index 0000000..e593de7
--- /dev/null
+++ b/testing/test_package_options/extras/footer-things.html
@@ -0,0 +1,2 @@
+Footer things.
+
diff --git a/testing/test_package_options/extras/footer.txt b/testing/test_package_options/extras/footer.txt
new file mode 100644
index 0000000..694c83c
--- /dev/null
+++ b/testing/test_package_options/extras/footer.txt
@@ -0,0 +1 @@
+footer.txt data
diff --git a/testing/test_package_options/extras/header.html b/testing/test_package_options/extras/header.html
new file mode 100644
index 0000000..1bd9ebc
--- /dev/null
+++ b/testing/test_package_options/extras/header.html
@@ -0,0 +1 @@
+HTML header file
diff --git a/testing/test_package_options/lib/main.dart b/testing/test_package_options/lib/main.dart
new file mode 100644
index 0000000..54ef155
--- /dev/null
+++ b/testing/test_package_options/lib/main.dart
@@ -0,0 +1,5 @@
+/// Reference an example in a non-default location, based on dartdoc_options.yaml.
+///
+/// {@example foo.dart}
+/// {@category Undocumented}
+class UseAnExampleHere {}
diff --git a/testing/test_package_options/package_examples/foo.dart.md b/testing/test_package_options/package_examples/foo.dart.md
new file mode 100644
index 0000000..879b5ae
--- /dev/null
+++ b/testing/test_package_options/package_examples/foo.dart.md
@@ -0,0 +1,3 @@
+```
+/// An example of an example in an unusual example location.
+```
diff --git a/testing/test_package_options/pubspec.yaml b/testing/test_package_options/pubspec.yaml
new file mode 100644
index 0000000..729c2b2
--- /dev/null
+++ b/testing/test_package_options/pubspec.yaml
@@ -0,0 +1,5 @@
+name: test_package_options
+version: 0.0.1
+description: A simple console application.
+environment:
+  sdk: '>=2.0.0 <3.0.0'
diff --git a/testing/test_package_options_importer/lib/main.dart b/testing/test_package_options_importer/lib/main.dart
new file mode 100644
index 0000000..63ee077
--- /dev/null
+++ b/testing/test_package_options_importer/lib/main.dart
@@ -0,0 +1,3 @@
+import 'package:test_package_options/main.dart';
+
+class X extends UseAnExampleHere {}
diff --git a/testing/test_package_options_importer/pubspec.yaml b/testing/test_package_options_importer/pubspec.yaml
new file mode 100644
index 0000000..b9e8fbc
--- /dev/null
+++ b/testing/test_package_options_importer/pubspec.yaml
@@ -0,0 +1,8 @@
+name: test_package_options_importer
+version: 0.0.1
+description: A simple console application.
+environment:
+  sdk: '>=2.0.0 <3.0.0'
+dependencies:
+  test_package_options:
+    path: '../test_package_options'