Send to Kindle

Wednesday, April 23, 2014

The Simplest Explanations (Polymer Testing)


I feel like my grip on reality is slightly less than it was yesterday. I can make a Polymer pass, but I have no idea why this makes it pass.

I have a very simply <x-double> Polymer element that takes the input of one attribute and doubles it on another attribute:
<polymer-element name="x-double" attributes="in out">
  <template></template>
  <script>
    Polymer("x-double", {
      ready: function(){
        this.inChanged();
      },
      inChanged: function(){
        this.out = parseInt(this.in) * 2;
      }
    });
  </script>
</polymer-element>
I have a correspondingly simple Karma / Jasmine test that tries to verify the element behaves as expected:
  it('works', function(){
    // Container element
    var container = document.createElement('div');

    // Polymer element for testing
    var el = document.createElement('x-double');
    el.setAttribute('out', '');
    el.setAttribute('in', '42');
    container.appendChild(el);
    document.body.appendChild(container);

    // Wait for the Polymer element to update itself...
    var done = false;
    waitsFor(function(){ return done; });
    el.async(function(){ done = true; });

    // Check expectations...
    runs(function(){
      expect(el.getAttribute('out')).toEqual('84');
    });
  });
Well, maybe not that simple, but it ought to work based on all of the research that I have done for Patterns in Polymer. So, naturally, the test fails:
Expected '' to equal '84'.
        Error: Expected '' to equal '84'.
            at null.<anonymous> (/home/chris/repos/angular-bind-polymer/test/BindingSpec.js:20:38)
As I mentioned, I can make this pass, but only if I add a hack that I do not understand. I change the inChanged() watcher method to set the out attribute in addition to setting the out property:
      inChanged: function(){
        this.out = parseInt(this.in) * 2;
        this.setAttribute('out', parseInt(this.in) * 2);
      }
But why is that necessary? Setting the property should set the attribute. Or am I crazy? I go back to read the documentation and, yes, “property values are reflected back into their attribute counterpart.” So what absolutely stupid thing am I doing tonight to prevent that from happening?

Well, it turns out that what I am doing was not absolutely stupid, but it was certainly a little stupid. I failed to read the mailing list which including a message about a breaking change to—you guessed it—attribute binding. It seems that binding like this, is not a typical use-case. It is still supported, but it is opt-in now. I can live with that (though it would be nice if the docs were updated).

Anyhow, I remove the explicit setting of the attribute, and instead opt-in for property reflection. To do so, I add reflect: true to the published definition for my element:
<polymer-element name="x-double" attributes="in out">
  <template></template>
  <script>
    Polymer("x-double", {
      publish: {
        out: {value: 0, reflect: true}
      },
      ready: function(){
        this.inChanged();
      },
      inChanged: function(){
        this.out = parseInt(this.in) * 2;
      }
    });
  </script>
</polymer-element>
With that, I have my test passing. And a little of my sanity restored.



Day #43

Tuesday, April 22, 2014

A Woeful Approach to Dynamically Defining Polymer Elements


Some posts really get away from me. Like last night's fiasco.

Invariably, the posts that get away from me suffer from the same problem: me trying to do too much at once. Last night I tried setting up a Karma test suite from scratch, to test an AngularJS directive with a Polymer web component. Individually, I can do any of those things relatively quickly (as long as I remember that Angular tests need angular-mock, sheesh). Put them together, however, and 2 or 3 three things that don't quite work might as well be 20.

So tonight, I do what I really ought to know by now: bite off smaller chunks. Even though I have my Angular tests running again, there seems to be something not quite right with my Polymer tests. Thanks to my work in Patterns in Polymer, I have a fairly reliable Karma, Jasmine, and Polymer setup, so this annoys me. To eliminate this annoyance, I focus solely on it.

I comment out the various setup blocks from my tests that initialize Angular and the stuff that gets injected to work with directives. I update my karma.conf.js so that only the tests and Polymer setup are loaded on the test page:
    // ...
    // list of files / patterns to load in the browser
    files: [
      'test/PolymerSetup.js',
      // 'bower_components/angular/angular.js',
      // 'bower_components/angular*/angular-*.js',
      // 'angular_bind_polymer.js',
      {pattern: 'bower_components/**', included: false, served: true},
      'test/*Spec.js'
    ],
    // ...
I will pull back in the Angular stuff once I have Polymer sorted out. As for Polymer, my setup remains as it had been in Patterns in Polymer. I dynamically add a <script> tag to load in the polymer platform and then wait for the polymer-ready event before proceeding with tests in the test/*Spec.js files:
script.src = "/base/bower_components/platform/platform.js";
document.getElementsByTagName("head")[0].appendChild(script);

// Delay Jasmine specs until polymer-ready
var POLYMER_READY = false;
beforeEach(function(done) {
  window.addEventListener('polymer-ready', function(){
    POLYMER_READY = true;
  });
  waitsFor(function(){return POLYMER_READY;});
});
I assume that works since I copied and pasted it from book tests that still pass. What I am unsure about is how to add dynamic Polymer element definitions to the page. When testing real polymer elements, I can dynamically create the <link> import in PolymerSetup (after the platform is initialized):
var link = document.createElement("link");
link.rel = "import";
link.href = "/base/elements/x-pizza.html";
document.getElementsByTagName("head")[0].appendChild(link);
I do not have a real element in this case. I just need a dummy Polymer element against which I can run my data-binding test. So instead of putting those files in a separate Polymer definition file and dynamically creating the <link> import element, I try to dynamically add the definition right to the page:
var double_el = document.createElement('polymer-element');
double_el.setAttribute('name', 'x-double');
double_el.setAttribute('attributes', 'in out');
double_el.innerHTML = '<script>\n'
                       + 'Polymer("x-double", {\n'
                       + '  in: 13,\n'
                       + '  ready: function(){ '
                       + '    console.log("+++++")'
                       + '    this.inChanged();'
                       + '    console.log("+++++")'
                       + '  },\n'
                       + '  inChanged: function(){\n'
                       + '    console.log("u0000000");\n'
                       + '    this.out = parseInt(this.in) * 2;\n'
                       + '  }\n'
                       + '});\n'
                       + '</script>\n';
document.body.appendChild(double_el);
For good measure, I also pull in the Polymer library itself (only the platform was loaded previously):
var link = document.createElement("link");
link.rel = 'import';
link.href = "/base/bower_components/polymer/polymer.html";
document.getElementsByTagName("head")[0].appendChild(link);
With that, I get… nothing. No console statements printed and no errors.

Still, that tells me something. Since there are no errors, the call to Polymer inside the <script> tags is working. The element definition even shows up in the DOM inspector:



Rather than try to solve multiple issues tonight, I really am just going to do the simplest thing that should work: moving the Polymer element definition out into a separate file. First, I will serve this fixture definition in Karma (but not load it by default):
    // ...
    // list of files / patterns to load in the browser
    files: [
      'test/PolymerSetup.js',
      // 'bower_components/angular/angular.js',
      // 'bower_components/angular*/angular-*.js',
      // 'angular_bind_polymer.js',
      {pattern: 'bower_components/**', included: false, served: true},
      {pattern: 'test/*.html', included: false, served: true},
      'test/*Spec.js'
    ],
    // ...
Then, I create my dummy Polymer element as test/x-double.html:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="x-double" attributes="in out">
  <script>
    Polymer("x-double", {
      in: 13,
      ready: function(){
        console.log("+++++")
        this.inChanged();
        console.log("+++++")
      },
      inChanged: function(){
        console.log("u0000000");
        this.out = parseInt(this.in) * 2;
      }
    });
  </script>
</polymer-element>
And that work just fine. All of those debug console statements pass. My test checking the doubling passed. Everything.

So it seems that something about the way that I am dynamically adding the element definition to my test page is wrong. Armed with the knowledge that the Polymer definition itself is sound, I am ready to try the next small steps.

Tomorrow.


Day #42

Monday, April 21, 2014

Failing to Test Angular + Polymer


Gah! Finally got the 1.1 edition of Patterns in Polymer out the door and there is still much to do on it. Most of that will have go on the back burner until I return from FITC next week, but I must keep the chain unbroken. So...

Tonight, I try to write a test for angular-bind-polymer, which is all the more relevant after discovering that much of the overhead with which I had burdened that AngularJS directive is no longer needed. Since this is an Angular directive test, Karma is the way to go for testing.

I run through karma init accepting defaults, save for file loading “globs” that make my code in the top-level of the repository and the tests in the test subdirectory:
What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
> "*.js"
> test/*Spec.js
> 
In order to test that this Angular directive works with Polymer, I will have to install Polymer as a development dependency. So I modify the Bower package bower.json configuration file, so that Polymer is listed in devDependencies:
{
  "name": "angular-bind-polymer",
  "version": "0.0.2",
  // ...
  "dependencies": {
    "angular": "~1.2.16"
  },
  "devDependencies": {
    "polymer": "Polymer/polymer"
  }
}
After a quick bower update, I am ready to go.

Except not quite. I need to add a few more dependencies:

    // ...
    files: [
      'bower_components/angular/angular.js',
      'bower_components/platform/platform.js',
      'angular_bind_polymer.js',
      {pattern: 'bower_components/**', included: false, served: true},
      'test/*Spec.js'
    ],
    // ...
And even then I am not seeing any of the angualar functions. Bother.

I eventually track this down to the lack of angular-mocks. I never remember that this is an inviolate requirement for testing AngularJS code—I see the name “mock” and assume that it is just for mocks and spies. Ah well, I update bower.json again:
{
  "name": "angular-bind-polymer",
  "version": "0.0.2",
  // ...
  "dependencies": {
    "angular": "~1.2.16"
  },
  "devDependencies": {
    "polymer": "Polymer/polymer",
    "angular-mocks": "~1.2.16"
  }
}
With that, I have my tests running… but failing. I am unsure if this is due to Polymer load order, Angular load order, or something else. But that seems like a good puzzle for another day.




Day #41

Sunday, April 20, 2014

Watching Polymer Attributes Is Now a Whole Lot Easier



With the 1.1 edition of Patterns in Polymer due in but a few hours, I do not have much time for blog posts, but the gods of my chain must have their daily sacrifice.

So I try out my AngularJS application that uses Polymer web components. I would like to see if, as I found last night, some of the infrastructure that I have created around it is really still necessary.

Of course the application has to still work for that investigation. Naturally, it does not. After upgrading to the latest Polymer and AngularJS versions (0.2.2 and 1.2.16, respectively, at the time of this writing), I get a big ol' injector error:
Error {stack: "Error: [$injector:modulerr] Failed to instantiate …:8000/bower_components/angular/angular.js:21459:5", message: "[$injector:modulerr] Failed to instantiate module …er_components%2Fangular%2Fangular.js%3A1379%3A20)"}
 "Error: [$injector:modulerr] Failed to instantiate module pizzaStoreApp due to:
Error: [$injector:nomod] Module 'pizzaStoreApp' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
http://errors.angularjs.org/1.2.16/$injector/nomod?p0=pizzaStoreApp
    at http://localhost:8000/bower_components/angular/angular.js:78:12
    at http://localhost:8000/bower_components/angular/angular.js:1611:17
    ...
Almost lost in there is the root cause, which is that the pizzaStoreApp module is not available for injection. It is required by the root element of my single page application's HTML:
<!doctype html>
<html ng-app="pizzaStoreApp">
  <!- ... -->
</html>
This is one of those things where I have to wonder how it ever worked. My module is, in fact, named PizzaStoreApp, not pizzaStoreApp:
var PizzaStoreApp = angular.module('PizzaStoreApp', [
  'ngRoute',
  'eee-c.angularBindPolymer'
]);
Mercifully, that is an easy enough fix. After making the first letter in the module's name lowercase, everything is working fine. Everything including my angular-bind-polymer directive, which still ensures two-way binding of Angular and Polymer attributes so that Angular can see the current value of the <x-pizza>'s internal pizza state:



That is pretty nice to see. But what about all of the ceremony through which I went to ensure that Polymer was loaded and applied before processing my Angular directive?

Well, it seems that it is no longer necessary. I comment out my onPolymerReady() function whose returned promise completes when the observe property is seen:
      // When Polymer is ready, establish the bound variable watch
      // onPolymerReady().
      //   then(function(){
          // When Polymer sees a change to the bound variable,
          // $apply / $digest the changes here in Angular
          var observer = new MutationObserver(function() {
            scope.$apply();
          });
          observer.observe(polymer(), {attributes: true});

          for (var _attr in attrMap) {
            scope.$watch(
              function() {return element.attr(_attr);},
              function(value) {
                scope[attrMap[_attr]] = value;
              }
            );
          }
        // });
I am not even listening for a polymer-ready event here, but running the mutation observer code immediately upon evaluating the directive. And it works just fine. Angular-bind-polymer is still able to see the Polymer change and set the corresponding property in Angular.

That is very exciting.

I am a little skeptical that it works, but I try moving the various angular and polymer script/template load order around in the containing document—and the bound variables continue to update. I even try this in Firefox and Internet Explorer—and it still works.

I am unsure when this got fixed—or even if the fix was in Polymer or Angular. It matters only if there is a chance that the condition might return, but that is what tests are for—and it is easier to test that something continues to work than for a broken thing to start working. So yay!

Now back to editing...


Day #40

Saturday, April 19, 2014

Polymer Ready for Outside Changes


What are we doing tonight Brain? The same thing we do every night, Pinky—trying to see if something that didn't work two months back in Polymer works now.

This is proving to be a rough time to release a book on Polymer. My self-imposed deadline for edition 1.1 is tomorrow, but it's looking more and more like a 1.2 may need to follow sooner rather than later. Still, I have a deadline so I need to do what I can to get the book in the best shape possible.

Tonight the thing that didn't work a while back was establishing attribute observers on Polymer elements—from outside the Polymer. I first ran into this issue while trying to get Angular and Polymer attributes playing nicely. Pushing changes from Angular down to Polymer worked just fine, but it took quite a bit of fiddling for Angular to see changes from Polymer (eventually resulting in the angular-bind-polymer package).

I wound up incorporating a similar solution into a general approach to watching from the outside. I may not need it after, though it does help to explain the concept before the Angular chapter so I may keep it regardless.

So the real question is, do I need the onPolymerReady hack under normal circumstances? The onPolymerReady hack provided a callback that would be executed as soon as the element in question was decorated by Polymer:
function onPolymerReady(el, fn) {
  if (el.$) fn();
  else setTimeout(function(){onPolymerReady(el, fn);}, 10);
}
Here, I am waiting 10 milliseconds between checking the target element for Polymer's dollar-sign property. That worked well in angular-bind-polymer, but it seems like overkill on simpler pages like this test page. If I comment out the call to onPolymerReady and just start my watcher function immediately:
document.addEventListener("DOMContentLoaded", function(event) {
  var el = document.querySelector('hello-you');
  // onPolymerReady(el, function(){
    watchPolymer(el);
  // });
});
It still does not work:
unresolved is now: undefined watcher.js:14
your_name is now: a watcher.js:14
your_name is now: as watcher.js:14
your_name is now: asd watcher.js:14
your_name is now: asdf watcher.js:14
The element starts off as undefined, which would result in errors (especially when used with Angular). That said, it does work eventually. After a few changes to the Polymer element, the watcher function eventually does see real changes, which is news to me. I do wonder if this is new with MutationObservers (which are used under the covers) or with Polymer. This might make the Angular solution a little easier. Something to check another day.

What does work with no errors—and is more along the lines of the Polymer Way™—is listening for the polymer-ready event:
document.addEventListener("DOMContentLoaded", function(event) {
  var el = document.querySelector('hello-you');
  // onPolymerReady(el, function(){
  document.addEventListener('polymer-ready', function(){
    watchPolymer(el);
  });
});
Then it works from the outset.

Of course, in the JavaScript world, there is no guarantee of script load order and execution. This is why I ran into trouble with AngularJS and Polymer. Which library loaded first? Had the polymer-ready event already fired by the time the Angular directive was evaluated? Had it fired by the time the directive tried to bind to the Polymer element's attribute?

I have to agree that the feedback that prompted tonight's investigation was correct. I do not need this onPolymerReady hack—at least not to solve the problem presented in the chapter. So I will ease back on some of the claims that I make therein. Still, I think that I will leave this in place—of only to avoid discussing this craziness in the already tight Angular chapter.

And now, on to editing!



Day #39

Friday, April 18, 2014

Submitting Forms with Polymer Elements (Still) Doesn't Work


While working through the remaining errata for Patterns in Polymer, I came across one that states that you can put a vanilla form inside of a Polymer element and expect that it should form-submit to an HTTP server like any other form. I honestly do not know that I have ever thought to do that, let alone try it. Now is as good a time as any.

I did research submitting a Polymer element as part of a normal form. That is, the Polymer element is some kind of super date picker and it is embedded in a vanilla HTML form. The expectation was that <input> fields within the Polymer element would be submitted along with any other form data on the page. As I found, this was not the case—the Polymer data was not submitted. In the end, I came up with a MutationObserver workaround, which served as the basis for a chapter in the book.

So first things first, with the latest versions of the JavaScript version of the library and with Polymer.dart, does submitting forms that contain Polymer elements with <input> elements now work?

I am still working with the sample form that looks like:



The first and last text <input> fields are defined in the HTML of the main page while the middle <input> is defined inside the very simple <hello-you> element:



But, if I enter data in all text <input> fields and submit the form, only the form elements that are part of the main document's HTML are submitted:
plain_old_param:plain
distributed_param:projected
And, loading this up in the JavaScript version, I find the same thing.

So you cannot submit a normal HTML form with a Polymer element that contains its own <input> tags. The MutationObserver approach is still needed. A bit of a bummer, but I confess that I relieved that I do not have to rewrite the chapter!

That said, this was not what the errata report specifically addressed. Said reviewer indicated that, if the Polymer element contained both the <form> and the <input>, then submitting it should work just fine.

And, indeed, that does work. I copy the HTML from the page into the <template> of my Polymer element:
<polymer-element name="hello-you">
  <template>
    <form action="test" method="post">
      <h1>Plain-old Form</h1>
      <input type=text name="plain_old_param" placeholder="Plain-old form field">
      <input type=submit value=Play>
    </form>
  </template>
  <script type="application/dart;component=1" src="hello_you.dart"></script>
</polymer-element>
When I fill out that plain_old_param text <input> and submit the <form> contained entirely inside the Polymer element, I find that it is submitted:
plain_old_param:asdf
And, when I try the same approach in the JavaScript version, I find the same results.

So in the end, not much needs to change in that chapter. In my opinion, Polymer elements that serve as <input> fields are a more likely use-case than a Polymer element that contains an entire <form>, so the chapter seems a very worthwhile one to include in the book. Still, I go back to rework the wording a little to (hopefully) make the use-case clearer.


Day #38

Thursday, April 17, 2014

Bare Minimum Polymer.dart Testing


I finally have my Polymer.dart tests passing again. But that is insufficient for Patterns in Polymer. I have to ensure that I am not programming by coincidence, which is a poor programming practice and disastrous book writing practice.

James Hurford was kind enough to give me a head start on this. He noted that he was able to get my Polymer.dart tests passing even without a few things that I had previously thought required—things like async() calls to flush the Polymer platform and onPolymer futures to ensure that platform was in place.

James hypothesized that the non-necessity of these actions might be due to the dev-channel Dart that he is running whereas I am still on stable 1.3. I have 1.4 at the ready, but begin with 1.3 to put that to the test.

I am explicitly invoking the Polymer.onReady future as part of my test, so I will just comment that out from my setUp() block:
@initMethod
main() {
  setUp((){
    // schedule(()=> Polymer.onReady);
    // More setup here...
  });
  // Tests here...
}
The schedule() here is not built into Dart's very excellent unittest library. Rather it comes from the also excellent scheduled_test package which extends unittest with some asynchronous goodness.

With that commented out, I also remove the async() calls. This was pulled into my Dart code because it was, at one point, necessary in both JavaScript and Dart versions of Polymer elements. I am not using this directly in the tests, but rather as a method on the Page Object that I am using to interact with the Polymer element:
class XPizzaComponent {
  String get currentPizzaStateDisplay => // ...
  Future addWholeTopping(String topping) {
    /* ... */
    return flush();
  }
  Future flush() {
    var _completer = new Completer();
    el.async((_)=> _completer.complete());
    return _completer.future;
  }
}
The async() method on Polymer elements is supremely handy when testing. It tells the Polymer element to immediately process all outstanding asynchronous operations like updating bound variables. It also tells the Polymer platform to “flush,” which updates all UI elements.

This came in extremely handy when the test interacted with the Polymer element (e.g. adding a whole topping to the <x-pizza> element) and needed to ensure that all updates had taken place before checking expectations. To put James' hypothesis to the test, I change the flush() method to return nothing instead of a Future:
class XPizzaComponent {
  String get currentPizzaStateDisplay => // ...
  Future addWholeTopping(String topping) {
    // ...
    return flush();
  }
   void flush() {}
}
And, just as James had found my tests all still pass:
➜  dart git:(master) ✗ ./test/run.sh
#READY
CONSOLE MESSAGE: unittest-suite-wait-for-done
CONSOLE MESSAGE: PASS: [defaults] it has no toppings
CONSOLE MESSAGE: PASS: [adding toppings] updates the pizza state accordingly
CONSOLE MESSAGE: 
CONSOLE MESSAGE: All 2 tests passed.
CONSOLE MESSAGE: unittest-suite-success
CONSOLE WARNING: line 213: PASS
So what gives? This works just fine in both Dart 1.3 and 1.4 without worrying about asynchronous operations. How did that happen?

Well, it turns out that there is a little nod to the asynchronous nature of Polymer that is still implicit. The scheduled test library is running each of those schedules in separate event loops. And, when each schedule finishes, it allows the main Dart event loop to continue processing whatever else it needs to—like Polymer bound variables and platform flushes. I can verify this by removing all schedules from my setUp():
  setUp((){
    // schedule(()=> Polymer.onReady);

    // schedule((){
      _el = createElement('<x-pizza></x-pizza>');
      document.body.append(_el);

      xPizza = new XPizzaComponent(_el);
      // return xPizza.flush();
    // });

    currentSchedule.onComplete.schedule(() => _el.remove());
  });
With that, all tests fail:
➜  dart git:(master) ✗ ./test/run.sh
#EOF
#READY
CONSOLE MESSAGE: unittest-suite-wait-for-done
CONSOLE MESSAGE: FAIL: [defaults] it has no toppings
  Caught ScheduleError:
  | Expected: '{"firstHalfToppings":[],"secondHalfToppings":[],"wholeToppings":[]}'
  |   Actual: ''
  |    Which: is different. Both strings start the same, but the given value is missing the following trailing characters: {"firstHal ...

CONSOLE MESSAGE: FAIL: [adding toppings] updates the pizza state accordingly
  Caught ScheduleError:
  | Expected: '{"firstHalfToppings":[],"secondHalfToppings":[],"wholeToppings":["green peppers"]}'
  |   Actual: '{"firstHalfToppings":[],"secondHalfToppings":[],"wholeToppings":[""]}'
  |    Which: is different.
  | Expected: ... ppings":["green pepp ...
  |   Actual: ... ppings":[""]}
  |                         ^
  |  Differ at offset 66
CONSOLE MESSAGE:
CONSOLE MESSAGE: 0 PASSED, 2 FAILED, 0 ERRORS
CONSOLE MESSAGE: Uncaught Error: Exception: Some tests failed.
I can make that pass by adding either the original async()/flush() or the schedule for Polymer.onReady, but I do not need both. Even so, I think that I will likely wind up recommending both in Patterns in Polymer. It better mirrors the approach in the JavaScript Polymer. Also, I think they make conceptual sense.


Day #37