add support for x-dart-test link tags in the new test package

Fixes https://github.com/dart-lang/web-components/issues/14

R=sigmund@google.com

Review URL: https://codereview.chromium.org//1104453007
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92115e5..540363e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+#### 0.11.3
+  * Add support for the new `link[rel="x-dart-test"]` tags from the `test`
+    package to the transformer.
+
 #### 0.11.2
   * Copied `DomProxyMixin` from `custom_element_apigen` to this package and
     renamed it `CustomElementProxyMixin`. This can be mixed into any class that
diff --git a/lib/build/test_compatibility.dart b/lib/build/test_compatibility.dart
new file mode 100644
index 0000000..4687502
--- /dev/null
+++ b/lib/build/test_compatibility.dart
@@ -0,0 +1,79 @@
+// 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.
+
+/// Some Transformers to maintain compatibility with the new `test` package,
+/// since it doesn't use normal dart script tags in html. We get around this by
+/// using two transformers:
+///   - A transformer to convert <lint rel="x-dart-test"> intro dart script tags
+///     so that they can be processed by the rest of the transformers normally.
+///   - A second transformer to convert the output back, so that the `test`
+///     package can find a <link rel="x-dart-test"> tag after all the
+///     transformations are done.
+library web_components.build.test_compatability.dart;
+
+import 'dart:async';
+import 'package:barback/barback.dart';
+import 'package:html/dom.dart';
+import 'package:html/parser.dart';
+
+/// The name of the attribute that will be added to script tags that started out
+/// as <link rel="x-dart-test"> tags.
+const testAttribute = '_was_test';
+
+/// The first transformer that should be ran, this does a query selector for
+/// link[rel="x-dart-test"] and changes them to a normal dart script tag with a
+/// special `_was_test` attribute so we know to change it back later on.
+class RewriteXDartTestToScript extends _EntryPointOnlyTransformer {
+  RewriteXDartTestToScript(List<String> entryPoints) : super(entryPoints);
+
+  Future apply(Transform transform) {
+    return transform.primaryInput.readAsString().then((String html) {
+      var doc = parse(html);
+      for (var tag in doc.querySelectorAll('link[rel="x-dart-test"]')) {
+        tag.replaceWith(new Element.tag('script')
+          ..attributes['type'] = 'application/dart'
+          ..attributes['src'] = tag.attributes['href']
+          ..attributes[testAttribute] = '');
+      }
+      transform.addOutput(
+          new Asset.fromString(transform.primaryInput.id, doc.outerHtml));
+    });
+  }
+}
+
+/// The last transformer that should be ran, this does a query selector for
+/// `script[type="application/dart"][_was_test]` and changes matching elements
+/// back to `<link rel="x-dart-test">` tags.
+class RewriteScriptToXDartTest extends _EntryPointOnlyTransformer {
+  RewriteScriptToXDartTest(List<String> entryPoints) : super(entryPoints);
+
+  Future apply(Transform transform) {
+    return transform.primaryInput.readAsString().then((String html) {
+      var doc = parse(html);
+      var scripts = doc.querySelectorAll(
+          'script[type="application/dart"][$testAttribute]');
+      for (var tag in scripts) {
+        tag.replaceWith(new Element.tag('link')
+          ..attributes['rel'] = 'x-dart-test'
+          ..attributes['href'] = tag.attributes['src']);
+      }
+      transform.addOutput(
+          new Asset.fromString(transform.primaryInput.id, doc.outerHtml));
+    });
+  }
+}
+
+/// Internal base class to encapsulate the isPrimary logic.
+abstract class _EntryPointOnlyTransformer extends Transformer {
+  final List<String> entryPoints;
+
+  _EntryPointOnlyTransformer(this.entryPoints) : super();
+
+  bool isPrimary(AssetId id) {
+    if (!id.path.startsWith('test/')) return false;
+    if (entryPoints != null) return entryPoints.contains(id.path);
+    // If no entry point is supplied, then any html file is an entry point.
+    return id.path.endsWith('.html');
+  }
+}
diff --git a/lib/transformer.dart b/lib/transformer.dart
index e18bcf6..fe0ca34 100644
--- a/lib/transformer.dart
+++ b/lib/transformer.dart
@@ -12,6 +12,8 @@
 export 'build/import_inliner.dart';
 import 'build/script_compactor.dart';
 export 'build/script_compactor.dart';
+import 'build/test_compatibility.dart';
+export 'build/test_compatibility.dart';
 import 'build/web_components.dart';
 export 'build/web_components.dart';
 
@@ -41,6 +43,10 @@
     {String sdkDir}) {
   var phases = [];
 
+  /// Must happen first, temporarily rewrites <link rel="x-dart-test"> tags to
+  /// <script type="application/dart" _was_test></script> tags.
+  phases.add([new RewriteXDartTestToScript(options.entryPoints)]);
+
   // Must happen before the WebComponents transformer, grabs all dart scripts
   // and combines them into one bootstrap file.
   phases.add([new ScriptCompactorTransformer(options.entryPoints)]);
@@ -51,6 +57,11 @@
 
   // Inlines all html imports and removes all dart script tags in the process.
   phases.add([new ImportInlinerTransformer(options.entryPoints)]);
+
+  /// Must happen last, rewrites
+  /// <script type="application/dart" _was_test></script> tags to
+  /// <link rel="x-dart-test"> tags.
+  phases.add([new RewriteScriptToXDartTest(options.entryPoints)]);
   return phases;
 }
 
diff --git a/pubspec.yaml b/pubspec.yaml
index 9200661..b449783 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: web_components
-version: 0.11.2
+version: 0.11.3
 author: Polymer.dart Authors <web-ui-dev@dartlang.org>
 homepage: https://www.dartlang.org/polymer-dart/
 description: >
diff --git a/test/build/test_compatibility_test.dart b/test/build/test_compatibility_test.dart
new file mode 100644
index 0000000..981cf26
--- /dev/null
+++ b/test/build/test_compatibility_test.dart
@@ -0,0 +1,80 @@
+// 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 web_components.test.build.test_compatibility_test;
+
+import 'package:code_transformers/tests.dart';
+import 'package:web_components/build/test_compatibility.dart';
+import 'package:unittest/compact_vm_config.dart';
+import 'package:unittest/unittest.dart';
+
+var start = new RewriteXDartTestToScript(null);
+var end = new RewriteScriptToXDartTest(null);
+
+main() {
+  useCompactVMConfiguration();
+
+  testPhases('can rewrite x-dart-test link tags to script tags', [[start]], {
+    'a|test/index.html': '''
+        <!DOCTYPE html>
+        <html>
+          <head>
+            <link rel="x-dart-test" href="foo.dart">
+          </head>
+          <body></body>
+        </html>''',
+  }, {
+    'a|test/index.html': '''
+        <!DOCTYPE html>
+        <html>
+          <head>
+            <script type="application/dart" src="foo.dart" $testAttribute="">
+            </script>
+          </head>
+          <body></body>
+        </html>''',
+  }, [], StringFormatter.noNewlinesOrSurroundingWhitespace);
+
+
+  testPhases('can rewrite script tags to x-dart-test link tags', [[end]], {
+    'a|test/index.html': '''
+        <!DOCTYPE html>
+        <html>
+          <head>
+            <script type="application/dart" src="foo.dart" $testAttribute="">
+            </script>
+          </head>
+          <body></body>
+        </html>''',
+  }, {
+    'a|test/index.html': '''
+        <!DOCTYPE html>
+        <html>
+          <head>
+            <link rel="x-dart-test" href="foo.dart">
+          </head>
+          <body></body>
+        </html>''',
+  }, [], StringFormatter.noNewlinesOrSurroundingWhitespace);
+
+
+  testPhases('restores original application at the end', [[start], [end]], {
+    'a|test/index.html': '''
+        <!DOCTYPE html>
+        <html>
+          <head>
+            <link rel="x-dart-test" href="foo.dart">
+          </head>
+          <body></body>
+        </html>''',
+  }, {
+    'a|test/index.html': '''
+        <!DOCTYPE html>
+        <html>
+          <head>
+            <link rel="x-dart-test" href="foo.dart">
+          </head>
+          <body></body>
+        </html>''',
+  }, [], StringFormatter.noNewlinesOrSurroundingWhitespace);
+}
diff --git a/test/build/transformer_test.dart b/test/build/transformer_test.dart
index 369dc58..924b290 100644
--- a/test/build/transformer_test.dart
+++ b/test/build/transformer_test.dart
@@ -9,7 +9,7 @@
 import 'common.dart';
 
 var transformer = new WebComponentsTransformerGroup(
-    new TransformOptions(['web/index.html'], false));
+    new TransformOptions(['web/index.html', 'test/index.html'], false));
 var phases = [[transformer]];
 
 main() {
@@ -172,4 +172,35 @@
         </html>
         ''',
   }, [], StringFormatter.noNewlinesOrSurroundingWhitespace);
+
+  testPhases('test compatibility', phases, {
+    'a|test/index.html': '''
+        <!DOCTYPE html>
+        <html>
+          <head>
+            <link rel="x-dart-test" href="index.dart">
+            <script src="packages/test/dart.js"></script>
+          </head>
+          <body></body>
+        </html>
+        ''',
+    'a|test/index.dart': '''
+        library a;
+
+        main() {}
+        ''',
+    'initialize|lib/initialize.dart': mockInitialize,
+    'web_components|lib/html_import_annotation.dart': mockHtmlImportAnnotation,
+  }, {
+    'a|test/index.html': '''
+        <!DOCTYPE html>
+        <html>
+          <head>
+            <link rel="x-dart-test" href="index.bootstrap.initialize.dart">
+            <script src="packages/test/dart.js"></script>
+          </head>
+          <body></body>
+        </html>
+        ''',
+  }, [], StringFormatter.noNewlinesOrSurroundingWhitespace);
 }