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: PASSSo 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
Thanks for finding this out. I knew there was something fishy, but didn't think to try it without schedule(() {})
ReplyDeleteI did a quick test again, and it seems you do still need the async() and/or Polymer.onReady, but only if you've a sophisticated element like the markdown-markup element I created. Then I think that's a special case, where the markup is extracted from between the content tags, parsed, then the result is inserted back into the element. This takes a second render of the element, so we have to wait for it to finish that. Things like that will occur again and again I think. There are probably other combinations which need the delay caused by async() and Polymer.onReady, so I think you're right in still recommending this approach.
ReplyDelete