How Suite it is: Declarative XForms Submission Testing

Steven Pemberton, CWI Amsterdam

Version: 2020-07-30.

Contents

  1. Abstract
  2. Greetings from Amsterdam
  3. 1625
  4. XForms
  5. XForms testing
  6. XForms 2
  7. Dogfood
  8. Test Suite
  9. Test specification
  10. Test specification
  11. Test specification
  12. Test specification
  13. Test cases
  14. Test cases
  15. Test cases
  16. Test cases
  17. Test cases
  18. Test cases
  19. Test cases
  20. Test cases
  21. Test cases
  22. How a test works
  23. How a test works
  24. How a test works
  25. How a test works
  26. Test output
  27. Test output
  28. Test output
  29. Test output
  30. Test output
  31. Example success (live!)
  32. Example failure (live!)
  33. Use of @res
  34. Submission
  35. XForms submission steps
  36. Testing submission
  37. Server
  38. Server functions
  39. POST
  40. The Structure of Submission Tests
  41. Test Initialisation
  42. Test Initialisation
  43. Test Initialisation
  44. The result of the PUT
  45. The result of the PUT
  46. The result of the PUT
  47. The result of the PUT
  48. Placement
  49. Contractions
  50. Initiating the GET
  51. Initiating the GET
  52. Initiating the GET
  53. Example success
  54. Example failure
  55. Example Test: No Data
  56. Example Test: No Data
  57. Example Test: No Data
  58. Example Test: No Data
  59. Example Test: No Data
  60. Example Test: No Data
  61. No data
  62. Example test: Invalid data
  63. Example Test: Non-relevant Data
  64. Non-relevant data
  65. Result
  66. Example Test: URI serialization
  67. Example Test: URI serialization
  68. Example Test: URI serialization
  69. Example Test: URI serialization
  70. Output
  71. Super Late-breaking
  72. Output
  73. Future Work
  74. Headers
  75. Conclusion
  76. Learning
  77. Advert

Abstract

Submission, the process of sending data to a server and dealing with the response, is probably the hardest part of XForms to implement, and certainly involves the XForms element with the most attributes. This is largely due to legacy: XForms was designed to work with existing standards, and HTTP submission was designed before XML existed: the data representations are several, and on occasion byzantine.

Part of the process of producing a standard such as XForms is a test suite to check implementability of the specification. The original XForms test suite consisted of a large collection of XForms, one XForm per feature to be tested. These had to be run by hand, and the output inspected to determine if the test had passed.

As a part of the XForms 2.0 effort, a new test suite is being designed and built. This tests features by introspection, without user intervention, so that the XForm itself can report if it has passed or not. Current work within the test suite is on submission.

This paper gives an overview of how the test suite works, and discusses the issues involved with submission, the XForms approach to it, and how to go about introspecting something that has left the client before you can cast your eyes on it.

Greetings from Amsterdam

Westerkerk

1625

Where today's talk is coming from

XForms

A declarative programming language. (Designed to be accessible out of the box ;-) )

Recently celebrated 10 years of XForms 1.1

XForms 2.0 now in preparation.

XForms testing

XForms 1.0 and XForms 1.1 both had a Testsuite:

XForms 2

XForms 2.0 is in progress, and with it Testsuite 2:

Dogfood

One large XForm with XML description of the tests.


Loads and runs tests one by one, or allows individual tests to be run.

Test Suite

Test specification

The suite consists of chapters of tests containing:

<testsuite spec="XForms 2.0" version="0.4">
   <chapter name="about" title="About XForms"/>
   <chapter name="introduction" title="Introduction to XForms"/>
   <chapter name="structure" title="Document Structure">
      <test name="namespace" spec="structure-namespace">XForms namespace</test>
      <test name="3.2.1.a" spec="structure-attrs-common">id attribute</test>
      <test name="3.2.1.b" spec="structure-attrs-common">foreign attributes</test>
      ...

Test specification

The suite consists of chapters of tests containing:

<testsuite spec="XForms 2.0" version="0.4">
   <chapter number="1" name="about" title="About XForms"/>
   <chapter number="2" name="introduction" title="Introduction to XForms"/>
   <chapter number="3" name="structure" title="Document Structure">
      <test name="namespace" spec="structure-namespace">XForms namespace</test>
      <test name="3.2.1.a" spec="structure-attrs-common">id attribute</test>
      <test name="3.2.1.b" spec="structure-attrs-common">foreign attributes</test>
      ...

Test specification

The suite consists of chapters of tests containing:

<testsuite spec="XForms 2.0" version="0.4">
   <chapter number="1" name="about" title="About XForms"/>
   <chapter number="2" name="introduction" title="Introduction to XForms"/>
   <chapter number="3" name="structure" title="Document Structure">
      <test name="namespace" spec="structure-namespace">XForms namespace</test>
      <test name="3.2.1.a" spec="structure-attrs-common">id attribute</test>
      <test name="3.2.1.b" spec="structure-attrs-common">foreign attributes</test>
      ...

Test specification

The suite consists of chapters of tests containing:

<testsuite spec="XForms 2.0" version="0.4">
   <chapter number="1" name="about" title="About XForms"/>
   <chapter number="2" name="introduction" title="Introduction to XForms"/>
   <chapter number="3" name="structure" title="Document Structure">
      <test name="namespace" spec="structure-namespace">XForms namespace</test>
      <test name="3.2.1.a" spec="structure-attrs-common">id attribute</test>
      <test name="3.2.1.b" spec="structure-attrs-common">foreign attributes</test>
      ...

Test cases

Each test is a separate XForm containing any number of test cases, largely using a standard template.

The template includes an instance containing title, description and test cases.

<tests pass="" xmlns="">
   <description title="days-from-date()">days-from-date() function</description>
   <test pass="" res="" req="-1">1969-12-31</test>
   <test pass="" res="" req="0">1970-01-01</test>
   <test pass="" res="" req="1">1970-01-02</test>
   <test pass="" res="" req="4">1970-01-05T01:01:01.01Z</test>
     ...

Test cases

Each test is a separate XForm containing any number of test cases, largely using a standard template.

The template includes an instance containing title, description and test cases.

<tests pass="" xmlns="">
   <description title="days-from-date()">days-from-date() function</description>
   <test pass="" res="" req="-1">1969-12-31</test>
   <test pass="" res="" req="0">1970-01-01</test>
   <test pass="" res="" req="1">1970-01-02</test>
   <test pass="" res="" req="4">1970-01-05T01:01:01.01Z</test>
     ...

Test cases

Each test is a separate XForm containing any number of test cases, largely using a standard template.

The template includes an instance containing title, description and test cases.

<tests pass="" xmlns="">
   <description title="days-from-date()">days-from-date() function</description>
   <test pass="" res="" req="-1">1969-12-31</test>
   <test pass="" res="" req="0">1970-01-01</test>
   <test pass="" res="" req="1">1970-01-02</test>
   <test pass="" res="" req="4">1970-01-05T01:01:01.01Z</test>
     ...

Test cases

Each test case has (optional) values in its content used as parameters to the test, as well as attributes:

<tests pass="" xmlns="">
   <description title="days-from-date()">days-from-date() function</description>
   <test pass="" res="" req="-1">1969-12-31</test>
   <test pass="" res="" req="0">1970-01-01</test>
   <test pass="" res="" req="1">1970-01-02</test>
   <test pass="" res="" req="4">1970-01-05T01:01:01.01Z</test>
     ...

Test cases

Each test case has (optional) values in its content used as parameters to the test, as well as attributes:

<tests pass="" xmlns="">
   <description title="days-from-date()">days-from-date() function</description>
   <test pass="" res="" req="-1">1969-12-31</test>
   <test pass="" res="" req="0">1970-01-01</test>
   <test pass="" res="" req="1">1970-01-02</test>
   <test pass="" res="" req="4">1970-01-05T01:01:01.01Z</test>
     ...

Test cases

Each test case has (optional) values in its content used as parameters to the test, as well as attributes:

<tests pass="" xmlns="">
   <description title="days-from-date()">days-from-date() function</description>
   <test pass="" res="" req="-1">1969-12-31</test>
   <test pass="" res="" req="0">1970-01-01</test>
   <test pass="" res="" req="1">1970-01-02</test>
   <test pass="" res="" req="4">1970-01-05T01:01:01.01Z</test>
     ...

Test cases

Each test case has (optional) values in its content used as parameters to the test, as well as attributes:

<tests pass="" xmlns="">
   <description title="days-from-date()">days-from-date() function</description>
   <test pass="" res="" req="-1">1969-12-31</test>
   <test pass="" res="" req="0">1970-01-01</test>
   <test pass="" res="" req="1">1970-01-02</test>
   <test pass="" res="" req="4">1970-01-05T01:01:01.01Z</test>
     ...

Test cases

Each test case has (optional) values in its content used as parameters to the test, as well as attributes:

<tests pass="" xmlns="">
   <description title="days-from-date()">days-from-date() function</description>
   <test pass="" res="" req="-1">1969-12-31</test>
   <test pass="" res="" req="0">1970-01-01</test>
   <test pass="" res="" req="1">1970-01-02</test>
   <test pass="" res="" req="4">1970-01-05T01:01:01.01Z</test>
     ...

Test cases

Each test case has (optional) values in its content used as parameters to the test, as well as attributes:

The top-level tests element has an attribute pass that records if all cases have passed.
.

<tests pass="" xmlns="">
   <description title="days-from-date()">days-from-date() function</description>
   <test pass="" res="" req="-1">1969-12-31</test>
   <test pass="" res="" req="0">1970-01-01</test>
   <test pass="" res="" req="1">1970-01-02</test>
   <test pass="" res="" req="4">1970-01-05T01:01:01.01Z</test>
     ...

How a test works

bind elements set up the tests. Calculating the results:

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

(Note that this single bind sets up all the tests.)

Calculating whether each test has succeeded:

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

and whether all tests have succeeded:

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

How a test works

bind elements set up the tests. Calculating the results:

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

(Note that this single bind sets up all the tests.)

Calculating whether each test has succeeded:

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

and whether all tests have succeeded:

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

How a test works

bind elements set up the tests. Calculating the results:

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

(Note that this single bind sets up all the tests.)

Calculating whether each test has succeeded:

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

and whether all tests have succeeded:

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

How a test works

bind elements set up the tests. Calculating the results:

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

(Note that this single bind sets up all the tests.)

Calculating whether each test has succeeded:

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

and whether all tests have succeeded:

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

Test output

<group>
   <label class="title" ref="description/@title"/>
   <output class="block" ref="description"/>
   <output class="{@pass}" ref="@pass"/>
   <repeat ref="test">
      <output value="."/> → <output ref="@res"/>
      <output class="wrong" ref="@req[.!=../@res]"/>
   </repeat>
</group>

Test output

<group>
   <label class="title" ref="description/@title"/>
   <output class="block" ref="description"/>
   <output class="{@pass}" ref="@pass"/>
   <repeat ref="test">
      <output value="."/> → <output ref="@res"/>
      <output class="wrong" ref="@req[.!=../@res]"/>
   </repeat>
</group>

Test output

<group>
   <label class="title" ref="description/@title"/>
   <output class="block" ref="description"/>
   <output class="{@pass}" ref="@pass"/>
   <repeat ref="test">
      <output value="."/> → <output ref="@res"/>
      <output class="wrong" ref="@req[.!=../@res]"/>
   </repeat>
</group>

Test output

<group>
   <label class="title" ref="description/@title"/>
   <output class="block" ref="description"/>
   <output class="{@pass}" ref="@pass"/>
   <repeat ref="test">
      <output value="."/> → <output ref="@res"/>
      <output class="wrong" ref="@req[.!=../@res]"/>
   </repeat>
</group>

Test output

<group>
   <label class="title" ref="description/@title"/>
   <output class="block" ref="description"/>
   <output class="{@pass}" ref="@pass"/>
   <repeat ref="test">
      <output value="."/> → <output ref="@res"/>
      <output class="wrong" ref="@req[.!=../@res]"/>
   </repeat>
</group>

Example success (live!)

Example failure (live!)

Use of @res

These examples test a function, and @res gets set with the value of the function.

Other tests set the @res attribute in different ways, and you will shortly see lots of examples of this.

Submission

The process of sending data to a server and dealing with the response.

One of the harder parts of XForms to implement:

Submission is also hard to test, because the very thing you want to test has left the client before you get a chance to introspect.

XForms submission steps

Submission in XForms has a number of steps performed by the implementation:

  1. Identify and collect the data.
  2. Optionally select the relevant subset.
  3. Optionally validate the resulting data.
  4. Determine method, service and URI to use for submission.
  5. Determine which serialization format is to be used.
  6. Serialize.
  7. Submit the serialization.
  8. Await, and deal with the response
    • successful or not
    • whether there is returned data.

Testing submission

Since the serialization leaves the client without the possibility of introspection, you need to get it back into the client for checking.

The original testsuite used a server with a special echo CGI URI, that caused the submitted serialization just to be returned unchanged to the client:

action="http://xformstest.org/cgi-bin/echo.sh"

so that submission tests submitted the data, and then displayed the result, which the tester would have to inspect.

Server

The CGI solved part of the problem, but very few webservers actually implement all of HTTP.

Whilst not testing servers, we do need to test that XForms implementations correctly talk to servers that do implement all of HTTP.

Our solution has been to write a server [XContents] that supports all of HTTP, and can be run locally.

No need for an echo facility: you can now just do a PUT followed by a GET.

Server functions

The server is a fairly straightforward:

POST

The only interesting case is POST:

(Therefore, if you don't like the tags <post>...</post> when the file is created, you can first PUT an empty file, or one containing only the open and closing tags, e.g. <data></data>, before you POST to it.)

The Structure of Submission Tests

The general template for submission tests includes three instances (though not all tests need all three):

<instance id="tests">...</instance>

<instance id="data">...</instance>

<instance id="result">
   <data xmlns=""/>
</instance>

Test Initialisation

To ensure we are really getting the right result back, on start-up we initialise a req attribute to a random value, before submitting the data:

<action ev:event="xforms-ready">
   <setvalue ref="instance('data')/test[1]/@req" value="random()"/>
   <send submission="put"/>
</action>

The submission called put is responsible for the submission:

<submission id="put" ref="instance('data')"
   resource="test.xml" method="put" replace="none"/>

Test Initialisation

To ensure we are really getting the right result back, on start-up we initialise a req attribute to a random value, before submitting the data:

<action ev:event="xforms-ready">
   <setvalue ref="instance('data')/test[1]/@req" value="random()"/>
   <send submission="put"/>
</action>

The submission called put is responsible for the submission:

<submission id="put" ref="instance('data')"
   resource="test.xml" method="put" replace="none"/>

Test Initialisation

To ensure we are really getting the right result back, on start-up we initialise a req attribute to a random value, before submitting the data:

<action ev:event="xforms-ready">
   <setvalue ref="instance('data')/test[1]/@req" value="random()"/>
   <send submission="put"/>
</action>

The submission called put is responsible for the submission:

<submission id="put" ref="instance('data')"
   resource="test.xml" method="put" replace="none"/>

The result of the PUT

There are two things that can happen in response to this.

1) the submission succeeds, in which case we initiate retrieving the instance we just sent:

<action ev:event="xforms-submit-done">
   <send submission="get"/>
</action>

The result of the PUT

There are two things that can happen in response to this.

1) the submission succeeds, in which case we initiate retrieving the instance we just sent:

<action ev:event="xforms-submit-done">
   <send submission="get"/>
</action>

The result of the PUT

There are two things that can happen in response to this.

2) the submission fails, where we just set the result to an error message, and leave it at that:

<action ev:event="xforms-submit-error">
    <setvalue ref="test/@res"
              value="concat('xforms-submit-error on PUT: ', 
                            event('response-reason-phrase'))"/>
</action>

The result of the PUT

There are two things that can happen in response to this.

2) the submission fails, where we just set the result to an error message, and leave it at that:

<action ev:event="xforms-submit-error">
    <setvalue ref="test/@res"
              value="concat('xforms-submit-error on PUT: ', 
                            event('response-reason-phrase'))"/>
</action>

Placement

These two actions are placed in the body of the put submission:

<submission id="put" resource="test.xml" method="put" replace="none">
   <action ev:event="xforms-submit-done">
      <send submission="get"/>
   </action>
   <action ev:event="xforms-submit-error">
       <setvalue ref="test/@res"
                 value="concat('xforms-submit-error on PUT: ', 
                               event('response-reason-phrase'))"/>
   </action>
</submission>

Contractions

An action element that contains a single sub-action can be contracted.

This is equivalent and shorter:

<submission id="put" resource="test.xml" method="put" replace="none">
   <send ev:event="xforms-submit-done" submission="get"/>
   <setvalue ev:event="xforms-submit-error" ref="test/@res"
             value="concat('xforms-submit-error on PUT: ', 
                           event('response-reason-phrase'))"/>
</submission>

Initiating the GET

In the case of success, the submission called get is initiated, which GETs the resource, and stores it in the result instance:

<send ev:event="xforms-submit-done" submission="get"/>

<submission id="get" resource="test.xml" method="get" serialization="none" 
            replace="instance" instance="result">
  ...
</submission>

Initiating the GET

This too has two event listeners in its body -- in the case of success, the random number we generated earlier gets copied to the result attribute; in the case of a failure, an error message:

<submission id="get" resource="test.xml" method="get" serialization="none" 
            replace="instance" instance="result">
   <setvalue ev:event="xforms-submit-done" ref="test/@res" 
             value="instance('result')/test/@req"/>
   <setvalue ev:event="xforms-submit-error" ref="test/@res" 
             value="concat('xforms-submit-error on GET: ',
                           event('response-reason-phrase'))"/>
</submission>

Initiating the GET

This too has two event listeners in its body -- in the case of success, the random number we generated earlier gets copied to the result attribute; in the case of a failure, an error message:

<submission id="get" resource="test.xml" method="get" serialization="none" 
            replace="instance" instance="result">
   <setvalue ev:event="xforms-submit-done" ref="test/@res" 
             value="instance('result')/test/@req"/>
   <setvalue ev:event="xforms-submit-error" ref="test/@res" 
             value="concat('xforms-submit-error on GET: ',
                           event('response-reason-phrase'))"/>
</submission>

Example success

Here are examples of output. Note that this uses exactly the same output code as the earlier template.

A successful test

(Unlike most other examples in this talk, this is not live, but an image, since the server for these slides doesn't implement PUT)

Example failure

A failure case of a server not supporting PUT. (This one is live)

(Earlier tests had already tested features such as the correct events being sent on completion).

Example Test: No Data

Trying to submit with no data should generate an xforms-submit-error event with an error-type of no-data.

Example Test: No Data

<submission ref="nodata" resource="doesntmatter" replace="none">
   <setvalue ev:event="xforms-submit-error"
             ref="test/@res" value="event('error-type')"/>
   <setvalue ev:event="xforms-submit-done"
             ref="test/@res">submission incorrectly succeeded</setvalue>
</submission>

Example Test: No Data

<submission ref="nodata" resource="doesntmatter" replace="none">
   <setvalue ev:event="xforms-submit-error"
             ref="test/@res" value="event('error-type')"/>
   <setvalue ev:event="xforms-submit-done"
             ref="test/@res">submission/ incorrectly succeeded</setvalue>
</submission>

Example Test: No Data

<submission ref="nodata" resource="doesntmatter" replace="none">
   <setvalue ev:event="xforms-submit-error"
             ref="test/@res" value="event('error-type')"/>
   <setvalue ev:event="xforms-submit-done"
             ref="test/@res">submission incorrectly succeeded</setvalue>
</submission>

Example Test: No Data

<submission ref="nodata" resource="doesntmatter" replace="none">
   <setvalue ev:event="xforms-submit-error"
             ref="test/@res" value="event('error-type')"/>
   <setvalue ev:event="xforms-submit-done"
             ref="test/@res">submission incorrectly succeeded</setvalue>
</submission>

Example Test: No Data

<submission ref="nodata" resource="doesntmatter" replace="none">
   <setvalue ev:event="xforms-submit-error"
             ref="test/@res" value="event('error-type')"/>
   <setvalue ev:event="xforms-submit-done"
             ref="test/@res">submission incorrectly succeeded</setvalue>
</submission>

No data

Example test: Invalid data

If you try to submit invalid data, submission should also fail, so this example works similarly.

Example Test: Non-relevant Data

By default, non-relevant data is elided. We have data to submit:

<instance id="data">
   <tests pass="" xmlns="">
      <description title="Submission with relevance">
        Check that only relevant data is submitted by default.</description>
      <test pass="" res="" req=""/>
      <test pass="" res="" req="nonrelevant"/>
      <test pass="" res="" req="nonrelevant"/>
   </tests>
</instance>

We mark some elements as non-relevant:

<bind ref="instance('data')/test" relevant="@req!='nonrelevant'"/>

Non-relevant data

Initialise with a random number as before, submit, and GET it back, replacing the main instance:

<submission id="get" resource="test.xml" method="get" serialization="none" 
            replace="instance" instance="tests">
   <setvalue ev:event="xforms-submit-done" ref="test[1]/@res" 
             value="instance('data')/test[1]/@req"/> 
   <setvalue ev:event="xforms-submit-error" ref="test/@res" 
             value="concat('GET xforms-submit-error: ', 
                           event('response-reason-phrase'))"/>
</submission>

Values that were not relevant in the source data should not appear in the result.

Result

Relevance test

Example Test: URI serialization

One of the possible ways of serialising data to the server is as part of the URI, and this is often the case with the GET method.

test.xml?q=word&n=99

Introspecting this is easier, because the events that signal success or failure contain the initiating URI.

Example Test: URI serialization

So we prepare some data.

<data xmlns="">
   <value attr="">one</value>
   <text attr="attr" ibute="ibute">ual</text>
   <number>3.14159</number>
</data>

Example Test: URI serialization

The testcases instance contains the serialization we expect:

<tests pass="" xmlns="">
   <description title="Serialization on GET with three values">
       With GET, serialization is done in the URI in a simplified fashion. This tests several facets of values.
   </description>
   <test pass="" res=""
         req="test.xml?value=one&amp;text=ual&amp;number=3.14159"/>
</tests>

Example Test: URI serialization

We submit in the usual way, not caring if it succeeds or fails, since both events contain the URI we want:

<submission id="get" ref="instance('data')" resource="test.xml" 
            method="get" replace="none">
    <setvalue ev:event="xforms-submit-error" ref="test/@res" 
              value="event('resource-uri')"/>
    <setvalue ev:event="xforms-submit-done" ref="test/@res" 
              value="event('resource-uri')"/>
</submission>

Output

Super Late-breaking

Of course, we can package lots of these up in a single instance:

<instance id="tests">
   <tests name="Serialization on GET" pass="" xmlns="">
      <description>With GET, serialization is done in the URI in a simplified fashion.</description>
      <test pass="" res="" req="test.xml?data=value"><data attr="ignored">value</data></test>
      <test pass="" res="" req="test.xml?value=one&amp;text=text"><data><value this="is ignored">one</value><text>text</text></data></test>
      <test pass="" res="" req="test.xml?value=one&amp;text=ual&amp;number=3.14159">
         <data>
            <value attr="">one</value>
            <text attr="attr" ibute="ibute">ual</text>
            <number>3.14159</number>
         </data>
      </test>
      <test pass="" res="" req="test.xml?one=1&amp;two=2%266%3B&amp;three=3">
      ...

Output

Future Work

Work in progress: many tests have still to be added, which is a top priority.

Work will be done on the test infrastructure, in particular storing the results of tests, (easier now with PUT).

Headers

One aspect that will have to be treated differently is the generation of HTTP headers.

These are hidden in the protocol, and not part of the content and therefore hard to test. While headers returned from the server are accessible to an XForm, headers sent by the implementation are not.

An addition to the server will probably have to be added in order to be able to gain access to those parts of the protocol.

Conclusion

Self-testing through introspection makes life much easier for testers.

The new test infrastructure makes it much easier to add and update tests.

Although testing submission has challenges compared with other parts of XForms, even these can be overcome using a fairly uniform approach, without too much overhead.

One surprise:

Learning

Thanks @Steven Pemberton for your https://homepages.cwi.nl/~steven/forms/TestSuite/index.xhtml

In the desert of learning xforms this is a valuable and helpful contribution. Not only do I get to learn about the definitions but there is also a test suite download to play around with as well as right clicking on the page for the source and seeing how xforms are put together. Thanks.

-- John Reed on xmlcom slack channel

Advert

Declarative Amsterdam conference October 8-9

http://declarative.amsterdam/

Declarative Amsterdam