saved from the database (the ``create`` call has returned without
error).
+Mic check, is this working?
+---------------------------
+
+So far, features have been implemented, code has been worked and
+tentatively tried. However, there is no guarantee they will *keep
+working* as new changes are performed, new features added, …
+
+The original author (you, dear reader) could keep a notebook with a
+list of workflows to check, to ensure everything keeps working. And
+follow the notebook day after day, every time something is changed in
+the module.
+
+That gets repetitive after a while. And computers are good at doing
+repetitive stuff, as long as you tell them how to do it.
+
+So let's add test to the module, so that in the future the computer
+can take care of ensuring what works today keeps working tomorrow.
+
+.. note::
+
+ Here we're writing tests after having implemented the widget. This
+ may or may not work, we may need to alter bits and pieces of code
+ to get them in a testable state. An other testing methodology is
+ :abbr:`TDD (Test-Driven Development)` where the tests are written
+ first, and the code necessary to make these tests pass is written
+ afterwards.
+
+ Both methods have their opponents and detractors, advantages and
+ inconvenients. Pick the one you prefer.
+
+The first step of :doc:`testing` is to set up the basic testing
+structure:
+
+1. Creating a javascript file
+
+ .. patch::
+
+2. Containing a test section (and a few tests to make sure the tests
+ are correctly run)
+
+ .. patch::
+
+3. Then declaring the test file in the module's manifest
+
+ .. patch::
+
+4. And finally — after restarting OpenERP — navigating to the test
+ runner at ``/web/tests`` and selecting your soon-to-be-tested
+ module:
+
+ .. image:: module/testing_0.png
+ :align: center
+
+ the testing result do indeed match the test.
+
+The simplest tests to write are for synchronous pure
+functions. Synchronous means no RPC call or any other such thing
+(e.g. ``setTimeout``), only direct data processing, and pure means no
+side-effect: the function takes some input, manipulates it and yields
+an output.
+
+In our widget, only ``format_time`` fits the bill: it takes a duration
+(in milliseconds) and returns an ``hours:minutes:second`` formatting
+of it. Let's test it:
+
+.. patch::
+
+This series of simple tests passes with no issue. The next easy-ish
+test type is to test basic DOM alterations from provided input, such
+as (for our widget) updating the counter or displaying a record to the
+records list: while it's not pure (it alters the DOM "in-place") it
+has well-delimited side-effects and these side-effects come solely
+from the provided input.
+
+Because these methods alter the widget's DOM, the widget needs a
+DOM. Looking up :doc:`a widget's lifecycle <widget>`, the widget
+really only gets its DOM when adding it to the document. However a
+side-effect of this is to :js:func:`~openerp.web.Widget.start` it,
+which for us means going to query the user's times.
+
+We don't have any records to get in our test, and we don't want to
+test the initialization yet! So let's cheat a bit: we can manually
+:js:func:`set a widget's DOM <openerp.web.Widget.setElement`, let's
+create a basic DOM matching what each method expects then call the
+method:
+
+.. patch::
+
.. [#DOM-building] they are not alternative solutions: they work very
well together. Templates are used to build "just
DOM", sub-widgets are used to build DOM subsections
--- /dev/null
+Index: web_example/static/src/tests/timer.js
+===================================================================
+--- /dev/null
++++ web_example/static/src/tests/timer.js
+@@ -0,0 +1 @@
++
--- /dev/null
+Index: web_example/static/src/tests/timer.js
+===================================================================
+--- web_example.orig/static/src/tests/timer.js
++++ web_example/static/src/tests/timer.js
+@@ -1 +1,8 @@
+-
++openerp.testing.section('timer', function (test) {
++ test('successful test', function () {
++ ok(true, "should work");
++ });
++ test('unsuccessful test', function () {
++ ok(false, "shoud fail");
++ });
++});
--- /dev/null
+Index: web_example/__openerp__.py
+===================================================================
+--- web_example.orig/__openerp__.py
++++ web_example/__openerp__.py
+@@ -8,4 +8,5 @@
+ 'js': ['static/src/js/first_module.js'],
+ 'css': ['static/src/css/web_example.css'],
+ 'qweb': ['static/src/xml/web_example.xml'],
++ 'test': ['static/src/tests/timer.js'],
+ }
--- /dev/null
+Index: web_example/static/src/tests/timer.js
+===================================================================
+--- web_example.orig/static/src/tests/timer.js
++++ web_example/static/src/tests/timer.js
+@@ -1,8 +1,45 @@
+ openerp.testing.section('timer', function (test) {
+- test('successful test', function () {
+- ok(true, "should work");
+- });
+- test('unsuccessful test', function () {
+- ok(false, "shoud fail");
++ test('format_time', function (instance) {
++ var w = new instance.web_example.Action();
++
++ strictEqual(
++ w.format_time(0),
++ '00:00:00');
++ strictEqual(
++ w.format_time(543),
++ '00:00:00',
++ "should round sub-second times down to zero");
++ strictEqual(
++ w.format_time(5340),
++ '00:00:05',
++ "should floor sub-second extents to the previous second");
++ strictEqual(
++ w.format_time(60000),
++ '00:01:00');
++ strictEqual(
++ w.format_time(3600000),
++ '01:00:00');
++ strictEqual(
++ w.format_time(86400000),
++ '24:00:00');
++ strictEqual(
++ w.format_time(604800000),
++ '168:00:00');
++
++ strictEqual(
++ w.format_time(22733958),
++ '06:18:53');
++ strictEqual(
++ w.format_time(41676639),
++ '11:34:36');
++ strictEqual(
++ w.format_time(57802094),
++ '16:03:22');
++ strictEqual(
++ w.format_time(73451828),
++ '20:24:11');
++ strictEqual(
++ w.format_time(84092336),
++ '23:21:32');
+ });
+ });
--- /dev/null
+Index: web_example/static/src/tests/timer.js
+===================================================================
+--- web_example.orig/static/src/tests/timer.js
++++ web_example/static/src/tests/timer.js
+@@ -42,4 +42,25 @@ openerp.testing.section('timer', functio
+ w.format_time(84092336),
+ '23:21:32');
+ });
++ test('update_counter', function (instance, $fixture) {
++ var w = new instance.web_example.Action();
++ // $fixture is a DOM tree whose content gets cleaned up before
++ // each test, so we can add whatever we need to it
++ $fixture.append('<div class="oe_web_example_timer">');
++ // Then set it on the widget
++ w.setElement($fixture);
++
++ // Update the counter with a known value
++ w.update_counter(22733958);
++ // And check the DOM matches
++ strictEqual($fixture.text(), '06:18:53');
++
++ w.update_counter(73451828)
++ strictEqual($fixture.text(), '20:24:11');
++ });
++ test('display_record', function (instance, $fixture) {
++ var w = new instance.web_example.Action();
++ $fixture.append('<ol class="oe_web_example_saved">')
++ ok(false, "test display records");
++ });
+ });
19
20
21
+22
+23
+24
+25
+26
Any override to :js:func:`~openerp.web.Widget.renderElement` which
does not call its ``_super`` **must** call
:js:func:`~openerp.web.Widget.setElement` with whatever it
- generated or the widget's behavior is undefined.r
+ generated or the widget's behavior is undefined.
.. note::