Wednesday, August 27, 2014

When to TDD and When Not to TDD (an exercise with Polymer.dart)


According to git lslt, it has been six months since I last updated the parent events chapter in Patterns in Polymer. Six months!

I start my review, as always, with my tests:
$ pwd     
/home/chris/repos/polymer-book/book/code-dart/parent_events
$ ls -l test
total 0
lrwxrwxrwx 1 chris chris 11 Dec  7  2013 packages -> ../packages
Son of a… Bad developer! Bad author!

I will not produce another edition without complete test coverage of the examples in the book. I will not worry about the code in the “play” area of the repository—that is mostly meant for spiking new features or exploring new facets of Polymer. But there is no excuse for basing any programming book on top of code without tests. So, I start with…

Running the application. It's silly to start testing without some idea of what things look like, so I double-check that this still runs. I will worry about the tests after the fact (not everything in life requires TDD). First, I clean out the pubspec.yaml dependencies of some early-Polymer cruft, leaving me with simply:
name: parent_events
dependencies:
  polymer: any
dev_dependencies:
  scheduled_test: any
I pub upgrade and find:
$ pub upgrade
Resolving dependencies... (4.4s)
...
> polymer 0.12.0 (was 0.9.0+1) (8 newer versions available)
...
Changed 29 dependencies!
Craziness. I cannot believe that I still have Polymer.dart 0.9 code the book (the 8 newer versions are pre-releases).

I also update the smoke test page in the Dart Pub standard web directory to perform the proper setup:
<!doctype html>
<html lang="en">
  <head>
  <!-- ... -->
    <!-- Load platform polyfills -->
    <script src="packages/web_components/platform.js"></script>
    <script src="packages/web_components/dart_support.js"></script>

    <!-- Load component(s) -->
    <link rel="import" href="packages/parent_events/click-sink.html">

    <!-- Start Polymer -->
    <script type="application/dart">
      export 'package:polymer/init.dart';
    </script>
  </head>
  <!-- ... -->
</html>
That should do it. So I fire up Dart pub's built-in test web server (pub serve), access the page, click around, and… nothing:



If I recall correctly, the <click-sink> element added to the page should listen for page events. With another 6 months of Polymer under my belt, I am no longer sure this is a strong pattern, but I would at least like to get it working.

Saaaaaay... this is a good time to introduce some tests! In the pub standard test directory, I add the text page context:
<!doctype html>
<html>
<head>
  <!-- Load platforms polyfills -->
  <script src="packages/web_components/platform.js"></script>

  <!-- Load component(s) -->
  <link rel="import" href="packages/parent_events/click-sink.html">

  <!-- The actual tests -->
  <script type="application/dart" src="test.dart"></script>
  <script src="packages/unittest/test_controller.js"></script>
</head>
<body>
  <click-sink></click-sink>
</body>
</html>
That is similar to the regular Polymer.dart page, but to initialize and test Polymer elements, I have found this to be an effective approach. I normally create the Polymer element inside my test setup, but I anticipate these tests to be simple enough that I add <click-sink> directly to the page. I can always change later if the need arises.

In addition to the test page context, I also need to initialize Polymer inside my tests:
library click_sink_test;

import 'package:polymer/polymer.dart';
import 'package:scheduled_test/scheduled_test.dart';

main() {
  initPolymer();

  setUp((){
    schedule(()=> Polymer.onReady);
  });

  test('displays click events', (){
    expect(false, true);
  });
  test('generates click-sink-click events', (){
    expect(false, true);
  });
}
And look! I have two failing tests already in place. I could access my tests from the pub web server (which spins up a test server as well), but I prefer testing with content_shell for ease of transition to continuous integration testing. The arguments are arcane, but as I avowed Linux user, I am used to it:
$ content_shell --dump-render-tree test/index.html
CONSOLE WARNING: line 12: flushing %s elements
CONSOLE MESSAGE: unittest-suite-wait-for-done
CONSOLE MESSAGE: FAIL: displays click events
  Caught ScheduleError:
  | Expected: <true>
  |   Actual: <false>

CONSOLE MESSAGE: FAIL: generates click-sink-click events
  Caught ScheduleError:
  | Expected: <true>
  |   Actual: <false>

CONSOLE MESSAGE:
CONSOLE MESSAGE: 0 PASSED, 2 FAILED, 0 ERRORS
My first step is right out the TDD playbook: change the (error) message. To do so, I write an actual test:
  test('displays click events', (){
    var el = query('click-sink');
    schedule(()=> document.body.click());
    schedule(() {
      expect(el.shadowRoot.text, contains('Page clicked'));
    });
  });
When I click the document body, I expect that the <click-sink> Polymer element will contain a text message that includes the coordinates of the click. Running this test, I find that the message has indeed changed:
CONSOLE MESSAGE: FAIL: displays click events
  Caught ScheduleError:
  | Expected: contains 'Page clicked'
  |   Actual: '\n'
  |   '    \n'
  |   '      Click Anywhere on the Page\n'
  |   '      \n'
  |   '        \n'
  |   '      \n'
  |   '    \n'
  |   '  '
Now, why on earth is this failing?

It turns out that I am using old syntax for the Polymer attached life cycle method (that tends to happen with a 6+ month old dependency on an alpha state library):
@CustomTag('click-sink')
class ClickSinkElement extends PolymerElement {
  // ...
  ClickSinkElement.created(): super.created();
  enteredView() {
    super.enteredView();
    // ...
  }
}
Both Polymer.dart and JavaScript Polymer have long since standardized on the attached() callback method (called when the Polymer instance is attached to the container document). So the fix is simple enough:
@CustomTag('click-sink')
class ClickSinkElement extends PolymerElement {
  // ...
  ClickSinkElement.created(): super.created();
  attached() {
    super.attached();
    // ...
  }
}
And, just like that, I have my element working and a legitimate test to ensure that something like this never happens again:
CONSOLE MESSAGE: PASS: displays click events
I remain quite disappointed in myself for not having done this already. Hopefully this is a case of better (severely) late than never.


Day #165

No comments:

Post a Comment