The XForms 2.0 Test Suite

Steven Pemberton, CWI, Amsterdam

The author

Reviewer

"Please don't assume that the audience already knows XForms"

XForms, an introduction

Designed originally for forms, now much more general.

A declarative programming language.

Large projects have shown it can cut production costs and time by an order of magnitude.

Used world-wide by large corporations (e.g. BBC, NHS, Xerox, EMC, Governments)

Implementations from Belgium, France, Germany, NL, UK, USA.

Form and content

"The term form refers to the work's style, techniques and media used, and how the elements of design are implemented.

Content, on the other hand, refers to a work's essence, or what is being depicted." - Wikipedia

This is a nearly-perfect description of XForms

XForms, an overview

XForms is all about state. (Which means it meshes well with REST - Representational State Transfer).

The data can be internal or come from external sources.

You describe the data: properties and relationships.

Initially the system is in a state of stasis.

When a value changes, by whatever means, the system updates related values to bring it back to stasis.

This is like spreadsheets, but much more general.

The result is: programming is much easier, since the system does all the administrative work for you.

Declarative programming

Is about describing the problem, not the solution.

Declarative programming

Is about describing the problem, not the solution.

Simple example of procedural programming:

function f a: {
    x ← a
    x' ← (a + 1) ÷ 2
    epsilon ← 1.19209290e-07
    while abs(x − x') > epsilon × x: {
        x ← x'
        x' ← ((a ÷ x') + x') ÷ 2
    }
    return x'
}

This is a solution to something. But what?

Declarative programming

Is about describing the problem, not the solution.

Simple example of procedural programming:

function f a: {
    x ← a
    x' ← (a + 1) ÷ 2
    epsilon ← 1.19209290e-07
    while abs(x − x') > epsilon × x: {
        x ← x'
        x' ← ((a ÷ x') + x') ÷ 2
    }
    return x'
}

This is a solution to something. But what?

The square root of a number n is the number r such that r × r = n

Declarative Example

I've got a position in the world as x and y coordinates, and I want to display the map tile of that location at a certain zoom.

My data:

x, y, zoom

Openstreetmap has a REST interface for getting such a thing.

Ideally you would just say:

http://openstreetmap.org/<zoom>/<x>/<y>

Unfortunately, their interface has a couple of problems, that expose the implementation, rather than providing an opaque interface.

Map interface problems

The first problem: you have to mention that it is a PNG image.

http://openstreetmap.org/<zoom>/<x>/<y>.png

This is a mistake, because they can now never change that decision.

Although so many sites do:

you do not have to expose your media types in your URLs!

Mediatypes are part of the protocol, not the URL.

Map interface problems

The second problem, is that the coordinate system changes at each level of zoom. (This problem has more of a justification)

As you zoom out, there are in each axis half as many tiles, so there are ¼ as many tiles. And the interface indexes tiles, not locations.

So to get a tile:

  1. You have to know how big a tile is
  2. you have to calculate the correct index using this plus the zoom.

They now cannot change how big their tiles are.

Declarative Example

The data: x, y, zoom

scale = 226 - zoom
tilex = floor(x/scale)
tiley = floor(y/scale)
url = concat("http://tile.openstreetmap.org/", zoom, "/", tilex, "/", tiley, ".png")

That is really all that is needed (modulo syntax, which looks like this:)

<bind ref="tilex" calculate="floor(../x div ../scale)"/>

That's the form. Now the content:

<input ref="x" label="x"/>
<input ref="y" label="y"/>
<input ref="zoom" label="zoom"/>
<output ref="url" mediatype="image/*"/>

and the tile will be updated each time any of the values change.

Live tile with working zoom

Source

Map

Source

XForms, an overview

Separation of data from UI, similar to separation of style from content with CSS, with similar advantages.

Instances of data + properties.

<instance src="sale.xml"/>
<bind ref="something" property="something"/>

Properties include:

Whenever a value changes, the related values are automatically updated, in spread-sheet style.

Example properties: relevant and required

<bind ref="address/state"
      required="../country = 'USA'"
      label="State"/>

This means that state is only required for the USA. The field will always be visible.

<bind ref="address/state"
      relevant="../country = 'USA'"
      required="true()"
      label="State"/>

This means that state will only be visible for the USA, but once visible will be required.

Controls in the UI then bind to data nodes, inheriting their properties.

<input ref="address/state"/>

Relevance

Here is an example (try it). If you select USA as country, the control for state appears.

Source

Events and Actions

While typically XForms works automatically, it is possible to hook into the processing model to respond in special ways.

To allow this, there are events that announce changes in the state, and actions that effect changes to the state.

For instance the xforms-ready event announces that the system has initialised. You could respond to this for instance by setting a value to today's date and time:

<action ev:event="xforms-ready">
   <setvalue ref="today" value="now()"/>
</action>

Events and Actions

Other events announce when a value changes, or when it changes validity, relevance, etc.

Other useful actions include activating a button, and inserting and deleting elements and attributes from data.

In fact the only way to get a vanilla button to do anything is to listen for the activation events, and then respond with an action.

<trigger label="Restart">
   <action ev:event="DOMActivate">
      <setvalue ref="score" value="0"/>
   </action>
</trigger>

Test Suites

It is good practice to define a test suite to go along with a specification.

In fact at W3C it is required.

Advantages:

The XForms Test Suite

XForms 1 and 1.1 have a test-suite where each feature is tested by a separate file.

Source

XForms 2

A new version of XForms is in preparation, and therefore a new version of the test suite.

The test suite is a self-testing, data-driven form.

Source

(This is work in progress; the example above is live, but may not always work).

Individual test cases are data-driven too

This makes it really easy to add new tests to test cases.

Source

Test case

We want to test that this function call returns the right result:

boolean-from-string('true')

We enclose the parameter in an element:

<test>true</test>

add attributes for the required result, actual result, and whether the test case passes:

<test pass="" res="" req="true">true</test>

Many test cases, with optional description

<instance>
   <tests pass="" name="boolean-from-string() function" xmlns="">
      <description>...</description>
      <test pass="" res="" req="true">true</test>
      <test pass="" res="" req="true">TRUE</test>
      <test pass="" res="" req="true">tRue</test>
      <test pass="" res="" req="true">1</test>
      <test pass="" res="" req="false">false</test>
      <test pass="" res="" req="false">FALSE</test>
      <test pass="" res="" req="false">faLse</test>
      <test pass="" res="" req="false">0</test>
      <test pass="" res="" req="false">qwertyuiop</test>
      <test pass="" res="" req="false"></test>
      ...
   </tests>
</instance>

Checking

Calculate the results:

<instance>
   <tests pass="" name="boolean-from-string() function" xmlns="">
      <test pass="" res="" req="true">true</test>
   ...
</instance>

<bind ref="test/@res" calculate="boolean-from-string(..)"/>

(this is the only line that is different in any test file, apart from the content of the test instance).

Check whether each passes:

<bind ref="test/@pass" calculate="if(../@res = ../@req, 'yes', 'no')"/>

Check whether the whole set passes:

<bind ref="@pass" calculate="if(test[@pass!='yes'], 'FAIL', 'PASS')"/>

Standard controls for all test sets

These are also the same for each test set:

<group>
    <label class="title" ref="@name"/>
    <output class="block" ref="description"/>
    <output class="{@pass}" ref="@pass"/>
    <repeat ref="test">
        <output value="."/> → <output ref="@res"/> 
        <output class="wrong"
                value="if(@pass!='yes', concat(' expected: ', @req), '')"/>
    </repeat>
</group>

Here's the example running

Source

A fail (at time of writing)

Source

Datatypes

<test pass="" res="" req="invalid"/>
<test pass="" res="" req="valid">2018-01-20</test>
<test pass="" res="" req="invalid">2018/01/20</test>
<test pass="" res="" req="valid">-2018-01-20</test>
<test pass="" res="" req="invalid">+2018-01-20</test>
<test pass="" res="" req="invalid">02018-01-20</test>
<test pass="" res="" req="valid">12018-01-20</test>

Bind the type

<bind ref="test" type="date"/>

The easiest way to check if these are valid values:

<bind ref="test/@res" 
      calculate="if(valid(..), 'valid', 'invalid')"/>

XForms 2.0

However, the function valid is an XForms 2.0 feature.

One of our aims is to minimise the use of XForms 2.0 features in the infrastructure of the testbed (since these are the features most likely not to work (yet)).

So how do we work out if a data value is valid or not? By catching the events that announce validity.

Validity events

If a control is bound to a value, whenever the value changes, events are dispatched to the control announcing validity and other properties, which we can catch and record:

<repeat ref="test">
  <output ref=".">
    <action ev:event="xforms-valid">
      <setvalue ref="@res">valid</setvalue>
    </action>
    <action ev:event="xforms-invalid">
      <setvalue ref="@res">invalid</setvalue>
    </action>
  </output>
  ...

Initialisation

During initialisation certain events are sent to announce stages of initialisation.

Last is xforms-ready, announcing that the system is ready to go.

The problem: before xforms-ready, there is little you can do.

The original test suite just popped up a dialog box to announce each initialisation event as it occurred:

<action ev:event="xforms-model-construct">
   <message>xforms-model-construct received</message>
</action> 

Like this

Source

Initialisation

There's not much you can do during initialisation, but there's one more thing you can do: dispatch an event.

<action ev:event="xforms-model-construct">
   <dispatch name="XMC" delay="1000" ... />
</action>

This dispatches a new event, with a delay of 1 second (more than enough to allow the system to initialise), which we can then catch, and record:

<action ev:event="XMC">
   <setvalue ref="test[1]">xforms-model-construct received</setvalue
</action>

Like this

Source

Conclusion

Lots of flexibility.

Easy to add new test cases, and tests.

Much easier to run.

Much easier to tell if a test passes or fails.

A small number of tests still need interaction (Does clicking a button send the right event? Does now() really return today's date?). But once those are out of the way, the rest can self-test.

Still work in progress; next steps: run and record results automatically.

XForms 2

Check out the XForms 2.0 spec at https://www.w3.org/community/xformsusers/wiki/XForms_2.0

Join the group if you would like to help!