Create renderers for more model classes and update some tests (#2078)

* Create renderer for EnumField

* Create renderers for Typedef & TypeParamaters

* Create renderer for ModelElement

* Fix some tests and dartfmt
diff --git a/lib/src/model/enum.dart b/lib/src/model/enum.dart
index 0ff67e4..91c2e68 100644
--- a/lib/src/model/enum.dart
+++ b/lib/src/model/enum.dart
@@ -5,6 +5,7 @@
 // TODO(jcollins-g): Consider Enum as subclass of Container?
 import 'package:analyzer/dart/element/element.dart';
 import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/render/enum_field_renderer.dart';
 
 class Enum extends Class {
   Enum(ClassElement element, Library library, PackageGraph packageGraph)
@@ -32,24 +33,18 @@
 /// Enum's fields are virtual, so we do a little work to create
 /// usable values for the docs.
 class EnumField extends Field {
-  int _index;
+  int index;
 
   EnumField(FieldElement element, Library library, PackageGraph packageGraph,
       Accessor getter, Accessor setter)
       : super(element, library, packageGraph, getter, setter);
 
-  EnumField.forConstant(this._index, FieldElement element, Library library,
+  EnumField.forConstant(this.index, FieldElement element, Library library,
       PackageGraph packageGraph, Accessor getter)
       : super(element, library, packageGraph, getter, null);
 
   @override
-  String get constantValueBase {
-    if (name == 'values') {
-      return 'const List&lt;<wbr><span class="type-parameter">${field.enclosingElement.name}</span>&gt;';
-    } else {
-      return 'const ${field.enclosingElement.name}($_index)';
-    }
-  }
+  String get constantValueBase => EnumFieldRendererHtml().renderValue(this);
 
   @override
   List<ModelElement> get documentationFrom {
@@ -87,7 +82,7 @@
     if (name == 'index') return false;
     // If this is something inherited from Object, e.g. hashCode, let the
     // normal rules apply.
-    if (_index == null) {
+    if (index == null) {
       return super.isCanonical;
     }
     // TODO(jcollins-g): We don't actually document this as a separate entity;
diff --git a/lib/src/model/model_element.dart b/lib/src/model/model_element.dart
index e09ba79..e2e0f4f 100644
--- a/lib/src/model/model_element.dart
+++ b/lib/src/model/model_element.dart
@@ -27,6 +27,7 @@
 import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/model_utils.dart' as utils;
 import 'package:dartdoc/src/render/parameter_renderer.dart';
+import 'package:dartdoc/src/render/model_element_renderer.dart';
 import 'package:dartdoc/src/source_linker.dart';
 import 'package:dartdoc/src/tuple.dart';
 import 'package:dartdoc/src/utils.dart';
@@ -1144,8 +1145,7 @@
       return htmlEscape.convert(name);
     }
 
-    var classContent = isDeprecated ? ' class="deprecated"' : '';
-    return '<a${classContent} href="${href}">$name</a>';
+    return ModelElementRendererHtml().renderLinkedName(this);
   }
 
   /// Replace &#123;@example ...&#125; in API comments with the content of named file.
@@ -1370,37 +1370,14 @@
         warn(PackageWarning.invalidParameter,
             message: 'A @youtube directive has an invalid URL: '
                 '"${positionalArgs[2]}". Supported YouTube URLs have the '
-                'follwing format: https://www.youtube.com/watch?v=oHg5SJYRHA0.');
+                'following format: https://www.youtube.com/watch?v=oHg5SJYRHA0.');
         return '';
       }
       final String youTubeId = url.group(url.groupCount);
       final String aspectRatio = (height / width * 100).toStringAsFixed(2);
 
-      // Blank lines before and after, and no indenting at the beginning and end
-      // is needed so that Markdown doesn't confuse this with code, so be
-      // careful of whitespace here.
-      return '''
-
-<p style="position: relative;
-          padding-top: $aspectRatio%;">
-  <iframe src="https://www.youtube.com/embed/$youTubeId?rel=0"
-          frameborder="0"
-          allow="accelerometer;
-                 autoplay;
-                 encrypted-media;
-                 gyroscope;
-                 picture-in-picture"
-          allowfullscreen
-          style="position: absolute;
-                 top: 0;
-                 left: 0;
-                 width: 100%;
-                 height: 100%;">
-  </iframe>
-</p>
-
-'''; // String must end at beginning of line, or following inline text will be
-      // indented.
+      return ModelElementRendererHtml()
+          .renderYoutubeUrl(youTubeId, aspectRatio);
     });
   }
 
@@ -1531,45 +1508,8 @@
                 'parameter)');
       }
 
-      // Blank lines before and after, and no indenting at the beginning and end
-      // is needed so that Markdown doesn't confuse this with code, so be
-      // careful of whitespace here.
-      return '''
-
-<div style="position: relative;">
-  <div id="${overlayId}"
-       onclick="var $uniqueId = document.getElementById('$uniqueId');
-                if ($uniqueId.paused) {
-                  $uniqueId.play();
-                  this.style.display = 'none';
-                } else {
-                  $uniqueId.pause();
-                  this.style.display = 'block';
-                }"
-       style="position:absolute;
-              width:${width}px;
-              height:${height}px;
-              z-index:100000;
-              background-position: center;
-              background-repeat: no-repeat;
-              background-image: url(static-assets/play_button.svg);">
-  </div>
-  <video id="$uniqueId"
-         style="width:${width}px; height:${height}px;"
-         onclick="var $overlayId = document.getElementById('$overlayId');
-                  if (this.paused) {
-                    this.play();
-                    $overlayId.style.display = 'none';
-                  } else {
-                    this.pause();
-                    $overlayId.style.display = 'block';
-                  }" loop>
-    <source src="$movieUrl" type="video/mp4"/>
-  </video>
-</div>
-
-'''; // String must end at beginning of line, or following inline text will be
-      // indented.
+      return ModelElementRendererHtml()
+          .renderAnimation(uniqueId, width, height, movieUrl, overlayId);
     });
   }
 
diff --git a/lib/src/model/type_parameter.dart b/lib/src/model/type_parameter.dart
index fc5d0ae..158095d 100644
--- a/lib/src/model/type_parameter.dart
+++ b/lib/src/model/type_parameter.dart
@@ -5,6 +5,7 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:dartdoc/src/element_type.dart';
 import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/render/type_parameters_renderer.dart';
 
 class TypeParameter extends ModelElement {
   TypeParameter(
@@ -76,21 +77,11 @@
   bool get hasGenericParameters => typeParameters.isNotEmpty;
 
   String get genericParameters {
-    if (typeParameters.isEmpty) return '';
-
-    var joined = typeParameters
-        .map((t) => t.name)
-        .join('</span>, <span class="type-parameter">');
-    return '&lt;<wbr><span class="type-parameter">${joined}</span>&gt;';
+    return TypeParametersRendererHtml().renderGenericParameters(this);
   }
 
   String get linkedGenericParameters {
-    if (typeParameters.isEmpty) return '';
-
-    var joined = typeParameters
-        .map((t) => t.linkedName)
-        .join('</span>, <span class="type-parameter">');
-    return '<span class="signature">&lt;<wbr><span class="type-parameter">${joined}</span>&gt;</span>';
+    return TypeParametersRendererHtml().renderLinkedGenericParameters(this);
   }
 
   @override
diff --git a/lib/src/model/typedef.dart b/lib/src/model/typedef.dart
index 15a168e..3a13447 100644
--- a/lib/src/model/typedef.dart
+++ b/lib/src/model/typedef.dart
@@ -5,6 +5,7 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:dartdoc/src/element_type.dart';
 import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/render/typedef_renderer.dart';
 
 class Typedef extends ModelElement
     with SourceCodeMixin, TypeParameters, Categorization
@@ -20,18 +21,14 @@
   String get nameWithGenerics => '$name${super.genericParameters}';
 
   @override
-  String get genericParameters {
+  String get genericParameters =>
+      TypedefRendererHtml().renderGenericParameters(this);
+
+  List<TypeParameterElement> get genericTypeParameters {
     if (element is GenericTypeAliasElement) {
-      List<TypeParameterElement> genericTypeParameters =
-          (element as GenericTypeAliasElement).function.typeParameters;
-      if (genericTypeParameters.isNotEmpty) {
-        var joined = genericTypeParameters
-            .map((t) => t.name)
-            .join('</span>, <span class="type-parameter">');
-        return '&lt;<wbr><span class="type-parameter">${joined}</span>&gt;';
-      }
-    } // else, all types are resolved.
-    return '';
+      return (element as GenericTypeAliasElement).function.typeParameters;
+    }
+    return Iterable.empty();
   }
 
   @override
diff --git a/lib/src/render/enum_field_renderer.dart b/lib/src/render/enum_field_renderer.dart
new file mode 100644
index 0000000..c9096b0
--- /dev/null
+++ b/lib/src/render/enum_field_renderer.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:dartdoc/src/model/enum.dart';
+
+abstract class EnumFieldRenderer {
+  String renderValue(EnumField field);
+}
+
+class EnumFieldRendererHtml extends EnumFieldRenderer {
+  @override
+  String renderValue(EnumField field) {
+    if (field.name == 'values') {
+      return 'const List&lt;<wbr><span class="type-parameter">${field.enclosingElement.name}</span>&gt;';
+    } else {
+      return 'const ${field.enclosingElement.name}(${field.index})';
+    }
+  }
+}
diff --git a/lib/src/render/model_element_renderer.dart b/lib/src/render/model_element_renderer.dart
new file mode 100644
index 0000000..700e3f2
--- /dev/null
+++ b/lib/src/render/model_element_renderer.dart
@@ -0,0 +1,90 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:dartdoc/src/model/model_element.dart';
+
+abstract class ModelElementRenderer {
+  String renderLinkedName(ModelElement modelElement);
+
+  String renderYoutubeUrl(String youTubeId, String aspectRatio);
+
+  String renderAnimation(
+      String uniqueId, int width, int height, Uri movieUrl, String overlayId);
+}
+
+class ModelElementRendererHtml extends ModelElementRenderer {
+  @override
+  String renderLinkedName(ModelElement modelElement) {
+    var cssClass = modelElement.isDeprecated ? ' class="deprecated"' : '';
+    return '<a${cssClass} href="${modelElement.href}">${modelElement.name}</a>';
+  }
+
+  @override
+  String renderYoutubeUrl(String youTubeId, String aspectRatio) {
+    // Blank lines before and after, and no indenting at the beginning and end
+    // is needed so that Markdown doesn't confuse this with code, so be
+    // careful of whitespace here.
+    return '''
+
+<p style="position: relative;
+          padding-top: $aspectRatio%;">
+  <iframe src="https://www.youtube.com/embed/$youTubeId?rel=0"
+          frameborder="0"
+          allow="accelerometer;
+                 autoplay;
+                 encrypted-media;
+                 gyroscope;
+                 picture-in-picture"
+          allowfullscreen
+          style="position: absolute;
+                 top: 0;
+                 left: 0;
+                 width: 100%;
+                 height: 100%;">
+  </iframe>
+</p>
+
+'''; // Must end at start of line, or following inline text will be indented.
+  }
+
+  @override
+  String renderAnimation(
+      String uniqueId, int width, int height, Uri movieUrl, String overlayId) {
+    return '''
+
+<div style="position: relative;">
+  <div id="${overlayId}"
+       onclick="var $uniqueId = document.getElementById('$uniqueId');
+                if ($uniqueId.paused) {
+                  $uniqueId.play();
+                  this.style.display = 'none';
+                } else {
+                  $uniqueId.pause();
+                  this.style.display = 'block';
+                }"
+       style="position:absolute;
+              width:${width}px;
+              height:${height}px;
+              z-index:100000;
+              background-position: center;
+              background-repeat: no-repeat;
+              background-image: url(static-assets/play_button.svg);">
+  </div>
+  <video id="$uniqueId"
+         style="width:${width}px; height:${height}px;"
+         onclick="var $overlayId = document.getElementById('$overlayId');
+                  if (this.paused) {
+                    this.play();
+                    $overlayId.style.display = 'none';
+                  } else {
+                    this.pause();
+                    $overlayId.style.display = 'block';
+                  }" loop>
+    <source src="$movieUrl" type="video/mp4"/>
+  </video>
+</div>
+
+'''; // Must end at start of line, or following inline text will be indented.
+  }
+}
diff --git a/lib/src/render/type_parameters_renderer.dart b/lib/src/render/type_parameters_renderer.dart
new file mode 100644
index 0000000..879ca0f
--- /dev/null
+++ b/lib/src/render/type_parameters_renderer.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:dartdoc/src/model/type_parameter.dart';
+
+abstract class TypeParametersRenderer {
+  String renderGenericParameters(TypeParameters typeParameters);
+  String renderLinkedGenericParameters(TypeParameters typeParameters);
+}
+
+class TypeParametersRendererHtml extends TypeParametersRenderer {
+  @override
+  String renderGenericParameters(TypeParameters typeParameters) {
+    if (typeParameters.typeParameters.isEmpty) {
+      return '';
+    }
+    var joined = typeParameters.typeParameters
+        .map((t) => t.name)
+        .join('</span>, <span class="type-parameter">');
+    return '&lt;<wbr><span class="type-parameter">${joined}</span>&gt;';
+  }
+
+  @override
+  String renderLinkedGenericParameters(TypeParameters typeParameters) {
+    if (typeParameters.typeParameters.isEmpty) {
+      return '';
+    }
+    var joined = typeParameters.typeParameters
+        .map((t) => t.linkedName)
+        .join('</span>, <span class="type-parameter">');
+    return '<span class="signature">&lt;<wbr><span class="type-parameter">${joined}</span>&gt;</span>';
+  }
+}
diff --git a/lib/src/render/typedef_renderer.dart b/lib/src/render/typedef_renderer.dart
new file mode 100644
index 0000000..385225b
--- /dev/null
+++ b/lib/src/render/typedef_renderer.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:dartdoc/src/model/typedef.dart';
+
+abstract class TypedefRenderer {
+  String renderGenericParameters(Typedef typedef);
+}
+
+class TypedefRendererHtml extends TypedefRenderer {
+  @override
+  String renderGenericParameters(Typedef typedef) {
+    if (typedef.genericTypeParameters.isEmpty) {
+      return '';
+    }
+    var joined = typedef.genericTypeParameters
+        .map((t) => t.name)
+        .join('</span>, <span class="type-parameter">');
+    return '&lt;<wbr><span class="type-parameter">${joined}</span>&gt;';
+  }
+}
diff --git a/test/model_special_cases_test.dart b/test/model_special_cases_test.dart
index 782e33f..8abf5af 100644
--- a/test/model_special_cases_test.dart
+++ b/test/model_special_cases_test.dart
@@ -459,7 +459,7 @@
               PackageWarning.invalidParameter,
               'A @youtube directive has an invalid URL: '
               '"http://host/path/to/video.mp4". Supported YouTube URLs have '
-              'the follwing format: '
+              'the following format: '
               'https://www.youtube.com/watch?v=oHg5SJYRHA0.'),
           isTrue);
     });
@@ -470,7 +470,7 @@
               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: '
+              'Supported YouTube URLs have the following format: '
               'https://www.youtube.com/watch?v=oHg5SJYRHA0.'),
           isTrue);
     });
diff --git a/test/model_test.dart b/test/model_test.dart
index ccc3970..58572a7 100644
--- a/test/model_test.dart
+++ b/test/model_test.dart
@@ -10,6 +10,8 @@
 import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/render/category_renderer.dart';
 import 'package:dartdoc/src/render/parameter_renderer.dart';
+import 'package:dartdoc/src/render/enum_field_renderer.dart';
+import 'package:dartdoc/src/render/typedef_renderer.dart';
 import 'package:dartdoc/src/warnings.dart';
 import 'package:test/test.dart';
 
@@ -1984,20 +1986,19 @@
     });
 
     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'));
+      var valuesField = animal.constants.firstWhere((f) => f.name == 'values');
+      expect(valuesField, isNotNull);
+      expect(valuesField.constantValue,
+          equals(EnumFieldRendererHtml().renderValue(valuesField)));
+      expect(valuesField.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)'));
+      expect(
+          dog.constantValue, equals(EnumFieldRendererHtml().renderValue(dog)));
     });
 
     test('constants have correct indicies', () {
@@ -3410,9 +3411,12 @@
               'NewGenericTypedef&lt;<wbr><span class="type-parameter">T</span>&gt;'));
     });
 
-    test("generic parameters", () {
-      expect(t.genericParameters, equals(''));
-      expect(generic.genericParameters,
+    // TODO(jdkoren): Not easy to call TypedefRenderer directly because Typedef
+    // inspects its element member. Find a better way when we start to isolate
+    // renderer tests.
+    test("TypedefRendererHtml renders genericParameters", () {
+      expect(TypedefRendererHtml().renderGenericParameters(t), equals(''));
+      expect(TypedefRendererHtml().renderGenericParameters(generic),
           equals('&lt;<wbr><span class="type-parameter">S</span>&gt;'));
     });
   });