Declare the sequence of phases for the full polymer transform and add tests for
it.

R=jmesserly@google.com

Review URL: https://codereview.chromium.org//23011045

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@26574 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkg/polymer/lib/src/transform.dart b/pkg/polymer/lib/src/transform.dart
index 48b78e7..ff5de2b 100644
--- a/pkg/polymer/lib/src/transform.dart
+++ b/pkg/polymer/lib/src/transform.dart
@@ -3,8 +3,22 @@
 // BSD-style license that can be found in the LICENSE file.
 
 /** Transfomers used for pub-serve and pub-deploy. */
+// TODO(sigmund): move into a plugin directory when pub supports it.
 library polymer.src.transform;
 
+import 'package:observe/transform.dart';
+import 'transform/code_extractor.dart';
+import 'transform/import_inliner.dart';
+import 'transform/script_compactor.dart';
+
 export 'transform/code_extractor.dart';
 export 'transform/import_inliner.dart';
 export 'transform/script_compactor.dart';
+
+/** Phases to deploy a polymer application. */
+var phases = [
+  [new InlineCodeExtractor()],
+  [new ObservableTransformer()],
+  [new ImportedElementInliner()],
+  [new ScriptCompactor()]
+];
diff --git a/pkg/polymer/lib/src/transform/code_extractor.dart b/pkg/polymer/lib/src/transform/code_extractor.dart
index 5f7058f..0e6d4d5 100644
--- a/pkg/polymer/lib/src/transform/code_extractor.dart
+++ b/pkg/polymer/lib/src/transform/code_extractor.dart
@@ -25,22 +25,34 @@
     return getPrimaryContent(transform).then((content) {
       var document = parseHtml(content, inputId.path, transform.logger);
       int count = 0;
+      bool htmlChanged = false;
       for (var tag in document.queryAll('script')) {
-        if (tag.attributes['type'] == 'application/dart' &&
-            !tag.attributes.containsKey('src')) {
-          // TODO(sigmund): should we automatically include a library directive
-          // if it doesn't have one?
-          var filename = path.url.basename(inputId.path);
-          tag.attributes['src'] = '$filename.$count.dart';
-          var textContent = tag.nodes.first;
-          var id = inputId.addExtension('.$count.dart');
-          transform.addOutput(new Asset.fromString(id, textContent.value));
-          textContent.remove();
-          count++;
+        // Only process tags that have inline Dart code
+        if (tag.attributes['type'] != 'application/dart' ||
+          tag.attributes.containsKey('src')) {
+          continue;
         }
+        htmlChanged = true;
+
+        // Remove empty tags
+        if (tag.nodes.length == 0) {
+          tag.remove();
+          continue;
+        }
+
+        // TODO(sigmund): should we automatically include a library directive
+        // if it doesn't have one?
+        var filename = path.url.basename(inputId.path);
+        // TODO(sigmund): ensure this filename is unique (dartbug.com/12618).
+        tag.attributes['src'] = '$filename.$count.dart';
+        var textContent = tag.nodes.first;
+        var id = inputId.addExtension('.$count.dart');
+        transform.addOutput(new Asset.fromString(id, textContent.value));
+        textContent.remove();
+        count++;
       }
       transform.addOutput(new Asset.fromString(inputId,
-          count == 0 ? content : document.outerHtml));
+          htmlChanged ? document.outerHtml : content));
     });
   }
 }
diff --git a/pkg/polymer/lib/src/transform/script_compactor.dart b/pkg/polymer/lib/src/transform/script_compactor.dart
index aa14728..9c2550a 100644
--- a/pkg/polymer/lib/src/transform/script_compactor.dart
+++ b/pkg/polymer/lib/src/transform/script_compactor.dart
@@ -38,11 +38,22 @@
       var document = parseHtml(content, id.path, logger);
       var libraries = [];
       bool changed = false;
+      var dartLoaderTag = null;
       for (var tag in document.queryAll('script')) {
+        var src = tag.attributes['src'];
+        if (src != null) {
+          if (src == 'packages/polymer/boot.js') {
+            tag.remove();
+            continue;
+          }
+          var last = src.split('/').last;
+          if (last == 'dart.js' || last == 'testing.js') {
+            dartLoaderTag = tag;
+          }
+        }
         if (tag.attributes['type'] != 'application/dart') continue;
         tag.remove();
         changed = true;
-        var src = tag.attributes['src'];
         if (src == null) {
           logger.warning('unexpected script without a src url. The '
             'ScriptCompactor transformer should run after running the '
@@ -63,8 +74,18 @@
 
       var bootstrapId = id.addExtension('_bootstrap.dart');
       var filename = path.url.basename(bootstrapId.path);
-      document.body.nodes.add(parseFragment(
-            '<script type="application/dart" src="$filename"></script>'));
+
+      var bootstrapScript = parseFragment(
+            '<script type="application/dart" src="$filename"></script>');
+      if (dartLoaderTag == null) {
+        document.body.nodes.add(bootstrapScript);
+        document.body.nodes.add(parseFragment('<script type="text/javascript" '
+            'src="packages/browser/dart.js"></script>'));
+      } else if (dartLoaderTag.parent != document.body) {
+        document.body.nodes.add(bootstrapScript);
+      } else {
+        document.body.insertBefore(bootstrapScript, dartLoaderTag);
+      }
 
       var urls = libraries.map((id) => importUrlFor(id, bootstrapId, logger))
           .where((url) => url != null).toList();
@@ -97,11 +118,9 @@
       return null;
     }
 
-    var urlBuilder = path.url;
-    var upPath = urlBuilder.joinAll(
-        urlBuilder.split(sourceId.path).map((_) => '..'));
-    return urlBuilder.normalize(
-        urlBuilder.join(sourceId.path, upPath, id.path));
+    var builder = path.url;
+    return builder.relative(builder.join('/', id.path),
+        from: builder.join('/', builder.dirname(sourceId.path)));
   }
 }
 
diff --git a/pkg/polymer/test/run_all.dart b/pkg/polymer/test/run_all.dart
index 61afa25..cc666ed 100644
--- a/pkg/polymer/test/run_all.dart
+++ b/pkg/polymer/test/run_all.dart
@@ -20,6 +20,7 @@
 import 'transform/code_extractor_test.dart' as code_extractor_test;
 import 'transform/import_inliner_test.dart' as import_inliner_test;
 import 'transform/script_compactor_test.dart' as script_compactor_test;
+import 'transform/all_phases_test.dart' as all_phases_test;
 
 main() {
   var args = new Options().arguments;
@@ -40,6 +41,7 @@
   addGroup('transform/code_extractor_test.dart', code_extractor_test.main);
   addGroup('transform/import_inliner_test.dart', import_inliner_test.main);
   addGroup('transform/script_compactor_test.dart', script_compactor_test.main);
+  addGroup('transform/all_phases_test.dart', all_phases_test.main);
 
   endToEndTests('data/unit/', 'data/out');
 
diff --git a/pkg/polymer/test/transform/all_phases_test.dart b/pkg/polymer/test/transform/all_phases_test.dart
new file mode 100644
index 0000000..a644fe5
--- /dev/null
+++ b/pkg/polymer/test/transform/all_phases_test.dart
@@ -0,0 +1,206 @@
+// Copyright (c) 2013, 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 polymer.test.transform.script_compactor_test;
+
+import 'package:polymer/src/transform.dart';
+import 'package:unittest/compact_vm_config.dart';
+
+import 'common.dart';
+
+void main() {
+  useCompactVMConfiguration();
+
+  testPhases('no changes', phases, {
+      'a|web/test.html': '<!DOCTYPE html><html></html>',
+    }, {
+      'a|web/test.html': '<!DOCTYPE html><html></html>',
+    });
+
+  testPhases('observable changes', phases, {
+      'a|web/test.dart': _sampleObservable('A', 'foo'),
+      'a|web/test2.dart': _sampleObservableOutput('B', 'bar'),
+    }, {
+      'a|web/test.dart': _sampleObservableOutput('A', 'foo'),
+      'a|web/test2.dart': _sampleObservableOutput('B', 'bar'),
+    });
+
+  testPhases('single script', phases, {
+      'a|web/test.html':
+          '<!DOCTYPE html><html><head>'
+          '<script type="application/dart" src="a.dart"></script>',
+      'a|web/test.dart': _sampleObservable('A', 'foo'),
+    }, {
+      'a|web/test.html':
+          '<!DOCTYPE html><html><head></head><body>'
+          '<script type="application/dart" '
+          'src="test.html_bootstrap.dart"></script>'
+          '<script type="text/javascript" '
+          'src="packages/browser/dart.js"></script>'
+          '</body></html>',
+
+      'a|web/test.html_bootstrap.dart':
+          '''library app_bootstrap;
+
+          import 'package:polymer/polymer.dart';
+          import 'dart:mirrors' show currentMirrorSystem;
+
+          import 'a.dart' as i0;
+
+          void main() {
+            initPolymer([
+                'a.dart',
+              ], currentMirrorSystem().isolate.rootLibrary.uri.toString());
+          }
+          '''.replaceAll('\n          ', '\n'),
+      'a|web/test.dart': _sampleObservableOutput('A', 'foo'),
+    });
+
+  testPhases('single inline script', phases, {
+      'a|web/test.html':
+          '<!DOCTYPE html><html><head>'
+          '<script type="application/dart">'
+          '${_sampleObservable("B", "bar")}</script>',
+    }, {
+      'a|web/test.html':
+          '<!DOCTYPE html><html><head></head><body>'
+          '<script type="application/dart" '
+          'src="test.html_bootstrap.dart"></script>'
+          '<script type="text/javascript" '
+          'src="packages/browser/dart.js"></script>'
+          '</body></html>',
+
+      'a|web/test.html_bootstrap.dart':
+          '''library app_bootstrap;
+
+          import 'package:polymer/polymer.dart';
+          import 'dart:mirrors' show currentMirrorSystem;
+
+          import 'test.html.0.dart' as i0;
+
+          void main() {
+            initPolymer([
+                'test.html.0.dart',
+              ], currentMirrorSystem().isolate.rootLibrary.uri.toString());
+          }
+          '''.replaceAll('\n          ', '\n'),
+      'a|web/test.html.0.dart': _sampleObservableOutput("B", "bar"),
+    });
+
+  testPhases('several scripts', phases, {
+      'a|web/test.html':
+          '<!DOCTYPE html><html><head>'
+          '<script type="application/dart" src="a.dart"></script>'
+          '<script type="application/dart">'
+          '${_sampleObservable("B", "bar")}</script>'
+          '</head><body><div>'
+          '<script type="application/dart">'
+          '${_sampleObservable("C", "car")}</script>'
+          '</div>'
+          '<script type="application/dart" src="d.dart"></script>',
+      'a|web/a.dart': _sampleObservable('A', 'foo'),
+    }, {
+      'a|web/test.html':
+          '<!DOCTYPE html><html><head></head><body><div></div>'
+          '<script type="application/dart" '
+          'src="test.html_bootstrap.dart"></script>'
+          '<script type="text/javascript" '
+          'src="packages/browser/dart.js"></script>'
+          '</body></html>',
+
+      'a|web/test.html_bootstrap.dart':
+          '''library app_bootstrap;
+
+          import 'package:polymer/polymer.dart';
+          import 'dart:mirrors' show currentMirrorSystem;
+
+          import 'a.dart' as i0;
+          import 'test.html.0.dart' as i1;
+          import 'test.html.1.dart' as i2;
+          import 'd.dart' as i3;
+
+          void main() {
+            initPolymer([
+                'a.dart',
+                'test.html.0.dart',
+                'test.html.1.dart',
+                'd.dart',
+              ], currentMirrorSystem().isolate.rootLibrary.uri.toString());
+          }
+          '''.replaceAll('\n          ', '\n'),
+      'a|web/a.dart': _sampleObservableOutput('A', 'foo'),
+      'a|web/test.html.0.dart': _sampleObservableOutput("B", "bar"),
+      'a|web/test.html.1.dart': _sampleObservableOutput("C", "car"),
+    });
+
+  testPhases('with imports', phases, {
+      'a|web/index.html':
+          '<!DOCTYPE html><html><head>'
+          '<link rel="import" href="test2.html">'
+          '</head><body>'
+          '<script type="application/dart" src="b.dart"></script>'
+          '<script type="application/dart">'
+          '${_sampleObservable("C", "car")}</script>',
+      'a|web/b.dart': _sampleObservable('B', 'bar'),
+      'a|web/test2.html':
+          '<!DOCTYPE html><html><head>'
+          '</head><body><polymer-element>1'
+          '<script type="application/dart">'
+          '${_sampleObservable("A", "foo")}</script>'
+          '</polymer-element></html>',
+    }, {
+      'a|web/index.html':
+          '<!DOCTYPE html><html><head></head><body>'
+          '<polymer-element>1</polymer-element>'
+          '<script type="application/dart" '
+          'src="index.html_bootstrap.dart"></script>'
+          '<script type="text/javascript" '
+          'src="packages/browser/dart.js"></script>'
+          '</body></html>',
+      'a|web/index.html_bootstrap.dart':
+          '''library app_bootstrap;
+
+          import 'package:polymer/polymer.dart';
+          import 'dart:mirrors' show currentMirrorSystem;
+
+          import 'test2.html.0.dart' as i0;
+          import 'b.dart' as i1;
+          import 'index.html.0.dart' as i2;
+
+          void main() {
+            initPolymer([
+                'test2.html.0.dart',
+                'b.dart',
+                'index.html.0.dart',
+              ], currentMirrorSystem().isolate.rootLibrary.uri.toString());
+          }
+          '''.replaceAll('\n          ', '\n'),
+      'a|web/test2.html.0.dart': _sampleObservableOutput("A", "foo"),
+      'a|web/b.dart': _sampleObservableOutput('B', 'bar'),
+      'a|web/index.html.0.dart': _sampleObservableOutput("C", "car"),
+    });
+}
+
+String _sampleObservable(String className, String fieldName) => '''
+import 'package:observe/observe.dart';
+
+class $className extends ObservableBase {
+  @observable int $fieldName;
+  $className(this.$fieldName);
+}
+''';
+
+String _sampleObservableOutput(String className, String fieldName) => '''
+import 'package:observe/observe.dart';
+
+class $className extends ChangeNotifierBase {
+  int __\$$fieldName;
+  int get $fieldName => __\$$fieldName;
+  set $fieldName(int value) {
+    __\$$fieldName = notifyPropertyChange(const Symbol('$fieldName'), __\$$fieldName, value);
+  }
+  
+  $className($fieldName) : __\$$fieldName = $fieldName;
+}
+''';
diff --git a/pkg/polymer/test/transform/script_compactor_test.dart b/pkg/polymer/test/transform/script_compactor_test.dart
index d9a0a1c..b271e3c 100644
--- a/pkg/polymer/test/transform/script_compactor_test.dart
+++ b/pkg/polymer/test/transform/script_compactor_test.dart
@@ -27,6 +27,8 @@
           '<!DOCTYPE html><html><head></head><body>'
           '<script type="application/dart" '
           'src="test.html_bootstrap.dart"></script>'
+          '<script type="text/javascript" '
+          'src="packages/browser/dart.js"></script>'
           '</body></html>',
 
       'a|test.html_bootstrap.dart':
@@ -59,6 +61,8 @@
           '<!DOCTYPE html><html><head></head><body><div></div>'
           '<script type="application/dart" '
           'src="test.html_bootstrap.dart"></script>'
+          '<script type="text/javascript" '
+          'src="packages/browser/dart.js"></script>'
           '</body></html>',
 
       'a|test.html_bootstrap.dart':