I think that I have some pretty useful keyboard event handling code for Dart on my hands. I would like to think that it is useful enough that others might have some use for and, more importantly, might be able to help me improve. So tonight, I am going to extract it out of the ICE Code Editor codebase into its own library.
Dart boasts nothing short of freaking amazing library support. In some respects, breaking this code out into a library is trivial. I can create a repository, a
pubspec.yaml
file for my library, add the code to the lib
sub-directory and I am done. But this is Dart. This is a new language and a new paradigm for reusable browser code. As such, I am not going to half-ass this.Development of this new code was heavily driven by some pretty solid tests. I am not going to lose those tests. Also, since Dart, even at this early stage, has a pretty excellent package repository at Dart Pub, I will end this exercise by pushing my new package.
Getting started, I create a new repository. I choose to call it ctrl-alt-foo since handling keyboard shortcuts largely drove this effort:
➜ repos mkdir ctrl-alt-foo ➜ repos cd !$ ➜ repos cd ctrl-alt-foo ➜ ctrl-alt-foo git init . Initialized empty Git repository in /home/chris/repos/ctrl-alt-foo/.git/ ➜ ctrl-alt-foo git:(master) mkdir lib test ➜ ctrl-alt-foo git:(master) touch README.md LICENSE pubspec.yamlI add basic documentation to
README.md
and add the MIT license to LICENSE
. The pubspec.yaml
file needs only the most basic information at this point in order to make this a real Dart package:name: ctrl_alt_foo version: 0.0.1 description: Keyboard library to make keyboard events in Dart a pleasure. authors: - Chris Strom <chris@eeecomputes.com> homepage: https://github.com/eee-c/ctrl-alt-foo dependencies: unittest: anyI add those files to Git and am ready for the next step: installing dependencies.
Actually there are not many dependencies in this case—just unittest and its dependencies (I add
js
later). Dart's pub
command makes this easy enough:➜ ctrl-alt-foo git:(master) pub install Resolving dependencies............ Downloading unittest 0.6.15+3 from hosted... Downloading path 0.6.15+3 from hosted... Downloading meta 0.6.15+3 from hosted... Downloading stack_trace 0.6.15+3 from hosted... Dependencies installed!This reminds me that I need to dot-git-ignore the
packages
directory, along with a few other items:➜ ctrl-alt-foo git:(master) ✗ cat <<IGNORE > .gitignore packages pubspec.lock out docs IGNOREWith the preliminaries out of the way, I am ready to start extracting code and tests out of the ICE repository and into this new repository. I have 150+ passing tests in ICE, many of which cover this functionality. So the approach that I take is to leave those tests in place while I move the code. Once I have verified that everything is still working, I will move the tests as well.
I move the
key_event_x.dart
directly into the new package repository:➜ ice-code-editor git:(ctrl-alt-foo) mv lib/key_event_x.dart ../ctrl-alt-foo/lib/I want to verify my package locally before pushing it out to the public, so I update ICE's dependencies to a local path:
name: ice_code_editor #... dependencies: # ... ctrl_alt_foo: path: /home/chris/repos/ctrl-alt-fooThis will resolve as a legitimate Dart package just the way that a pub.dartlang.org or a github repository would but has the advantage of working locally without needing to pull and changes. Any work that I do in my local
ctrl-alt-foo
repository is immediately available in ICE thanks to the path
dependency. After a quick pub install
:➜ ice-code-editor git:(ctrl-alt-foo) ✗ pub install Resolving dependencies........ Dependencies installed! ➜ ice-code-editor git:(ctrl-alt-foo) ✗ ls -l packages total 20 lrwxrwxrwx 1 chris chris 34 Aug 11 22:55 ctrl_alt_foo -> /home/chris/repos/ctrl-alt-foo/lib ...I see my new package which should contain the recently relocated
key_event_x.dart
file. To use this package file, I need to change the import
statement in the ICE library file from the in-package version:library ice; // ... import 'key_event_x.dart';To use my new package:
library ice; // ... import 'package:ctrl_alt_foo/key_event_x.dart';With that, I am no longer using a local class file, but one in a new repository that is completely ready for publishing to pub.dartlang.org. A quick test run reveals that all of my tests still pass:
CONSOLE MESSAGE: unittest-suite-wait-for-done ... CONSOLE MESSAGE: PASS: Keyboard Shortcuts can open the new dialog CONSOLE MESSAGE: PASS: Keyboard Shortcuts Open Projects Dialog can open the project dialog CONSOLE MESSAGE: PASS: Keyboard Shortcuts Open Projects Dialog hitting enter with no filter text does nothing CONSOLE MESSAGE: PASS: Keyboard Shortcuts Open Projects Dialog enter opens the top project CONSOLE MESSAGE: PASS: Keyboard Shortcuts Open Projects Dialog tab key moves forward in list CONSOLE MESSAGE: PASS: Keyboard Shortcuts Open Projects Dialog down arrow key moves forward in list CONSOLE MESSAGE: PASS: Keyboard Shortcuts Open Projects Dialog up arrow key moves backward in list CONSOLE MESSAGE: PASS: Keyboard Shortcuts Open Projects Dialog up arrow at top of list moves back into filter CONSOLE MESSAGE: PASS: Keyboard Shortcuts Open Projects Dialog With Less Than 10 Projects first project has keyboard focus CONSOLE MESSAGE: PASS: Keyboard Shortcuts Open Projects Dialog With Less Than 10 Projects up arrow at top of list stays at top of list CONSOLE MESSAGE: PASS: Keyboard Shortcuts Open Projects Dialog With Less Than 10 Projects down arrow at bottom of list stays at bottom of list CONSOLE MESSAGE: PASS: Toggling the code editor with hotkey CONSOLE MESSAGE: PASS: Toggling the code editor with post message CONSOLE MESSAGE: All 154 tests passed. CONSOLE MESSAGE: unittest-suite-successEven though the ctrl_alt_foo package code is ready to go, it lacks something vitally important: tests. To be sure, the ICE tests cover the functionality, but I have no tests in my new package to describe how it works. That is simply not cool.
Unfortunately, I now realize that most of the tests in ICE do not directly apply to ctrl_alt_foo code. The ICE tests are higher-level, verifying things like Ctrl+N opens a new project dialog. I am exercising all of the functionality in ctrl_alt_foo, but the expectation can only be verified with a full installation of ICE. I could make ICE a dependency of ctrl_alt_foo, but since the latter depends on the former, that seems a bad idea. Besides, it should not take too much effort to write a few tests, based on the ICE tests, for my new library.
To run browser tests in Dart, a bit of boilerplate is needed. The purposes of each are documented elsewhere, but I need a shell script to run (for continuous integration), a web page to supply browser context and some test harness code, and a test file that runs the tests and interacts with the browser harness code.
The
test/run.sh
Bash script runs the code through dartanalyzer
and runs the tests under Chrome's content_shell
:#!/bin/bash # Static type analysis results=$(dartanalyzer test/test.dart 2>&1) echo "$results" if [[ "$results" == *"warnings found"* || "$results" == *"error"* ]] then exit 1 fi results=$(content_shell --dump-render-tree test/index.html 2>&1) echo -e "$results" if [[ "$results" == *"Some tests failed"* ]] then exit 1 fiThe
test/index.html
browser context file loads the test.dart
and then does a lot of work setting up the testing environment:<script type="application/dart" src="test.dart"></script> <script type='text/javascript'> var testRunner = window.testRunner || window.layoutTestController; if (testRunner) { function handleMessage(m) { if (m.data == 'done') { testRunner.notifyDone(); } } testRunner.waitUntilDone(); window.addEventListener("message", handleMessage, false); } </script> <script src="packages/browser/dart.js"></script> <script src="packages/browser/interop.js"></script>Lastly, the
test/test.dart
file imports the necessary packages (including the ctrl_alt_foo files that will be tested) runs the tests and starts polling for the asynchronous tests to complete:library ctrl_alt_foo_test; import 'package:ctrl_alt_foo/key_event_x.dart'; import 'package:unittest/unittest.dart'; import 'dart:html'; import 'dart:async'; import 'package:ctrl_alt_foo/helpers.dart'; main(){ pollForDone(testCases); } pollForDone(List tests) { if (tests.every((t)=> t.isComplete)) { window.postMessage('done', window.location.href); return; } var wait = new Duration(milliseconds: 100); new Timer(wait, ()=> pollForDone(tests)); }The
pollForDone
function in here and the testRunner
code in index.html
are intertwined and so boilerplate at this point that I ought to extract them into a separate package. Something for another day, perhaps.I write a few basic tests inside that
main()
entry point: // ...
test("can listen for key events", (){
KeyboardEventStreamX.onKeyDown(document).listen(expectAsync1((e) {
expect(e.isKey('A'), true);
}));
type('A');
});
test("can listen for Ctrl shortcuts", (){
KeyboardEventStreamX.onKeyDown(document).listen(expectAsync1((e) {
expect(e.isCtrl('A'), true);
}));
typeCtrl('A');
});
test("can listen for Ctrl-Shift shortcuts", (){
KeyboardEventStreamX.onKeyDown(document).listen(expectAsync1((e) {
expect(e.isCtrlShift('A'), true);
}));
typeCtrlShift('A');
});
// ...
And run the test runner:➜ ctrl-alt-foo git:(master) ✗ ./test/run.sh Analyzing test/test.dart... No issues found. CONSOLE MESSAGE: unittest-suite-wait-for-done CONSOLE MESSAGE: PASS: can listen for key events CONSOLE MESSAGE: PASS: can listen for Ctrl shortcuts CONSOLE MESSAGE: PASS: can listen for Ctrl-Shift shortcuts CONSOLE MESSAGE: CONSOLE MESSAGE: All 3 tests passed. CONSOLE MESSAGE: unittest-suite-success Content-Type: text/plain layer at (0,0) size 800x600 RenderView at (0,0) size 800x600 layer at (0,0) size 800x600 RenderBlock {HTML} at (0,0) size 800x600 RenderBody {BODY} at (8,8) size 784x584 #EOF #EOF #EOF ➜ ctrl-alt-foo git:(master) ✗ echo $? 0That looks solid for a first pass.
The last thing that I need to do tonight is make the code available on GitHub: https://github.com/eee-c/ctrl-alt-foo and “pub lish” it to Dart Pub. As easy as it is to get it on GitHub, pub makes publihsing my new package even easier:
➜ ctrl-alt-foo git:(master) pub lish Publishing "ctrl_alt_foo" 0.0.1: |-- .gitignore |-- LICENSE |-- README.md |-- lib | |-- helpers.dart | '-- key_event_x.dart |-- pubspec.yaml '-- test |-- index.html |-- run.sh '-- test.dart Looks great! Are you ready to upload your package (y/n)? y Uploading......... ctrl_alt_foo 0.0.1 uploaded successfully.And, just like that, I am done: http://pub.dartlang.org/packages/ctrl_alt_foo! I extracted perfectly usable code out into a separate, tested library and published that library as a package to Dart's pub.dartlang.org in a single night. On top of that, my running code never once experienced a hiccup during this process. ICE continues to work exactly as it had, only now I have pulled nearly 100 lines of non-domain code out into a library. Nice!
About the only thing that went wrong tonight was setting up the drone.io continuous integration process (I was forbidden for some reason). I will pick back up with that tomorrow.
Day #840
No comments:
Post a Comment