From 4418b440de1d4f5785ab36508ead646381c5c7c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Fri, 23 Oct 2020 09:20:07 +0200
Subject: [PATCH 2/3] misc: split form pages tests (block)

---
 tests/test_form_pages/test_all.py   | 760 +-------------------------
 tests/test_form_pages/test_block.py | 802 ++++++++++++++++++++++++++++
 2 files changed, 807 insertions(+), 755 deletions(-)
 create mode 100644 tests/test_form_pages/test_block.py

diff --git a/tests/test_form_pages/test_all.py b/tests/test_form_pages/test_all.py
index 093c2742..571b54e4 100644
--- a/tests/test_form_pages/test_all.py
+++ b/tests/test_form_pages/test_all.py
@@ -1,6 +1,5 @@
 # -*- coding: utf-8 -*-
 
-import datetime
 import json
 import pytest
 import hashlib
@@ -19,7 +18,6 @@ try:
 except ImportError:
     Image = None
 
-from django.utils import six
 from django.utils.six import StringIO, BytesIO
 from django.utils.six.moves.urllib import parse as urlparse
 
@@ -30,14 +28,13 @@ from wcs.qommon import force_str
 from wcs.qommon.emails import docutils
 from wcs.qommon.form import UploadedFile
 from wcs.qommon.ident.password_accounts import PasswordAccount
-from wcs.blocks import BlockDef
 from wcs.carddef import CardDef
 from wcs.formdef import FormDef
-from wcs.workflows import (Workflow, EditableWorkflowStatusItem,
-        DisplayMessageWorkflowStatusItem, WorkflowBackofficeFieldsFormDef,
-        ChoiceWorkflowStatusItem, JumpOnSubmitWorkflowStatusItem,
-        SendmailWorkflowStatusItem, CommentableWorkflowStatusItem,
-        WorkflowVariablesFieldsFormDef)
+from wcs.workflows import (
+    Workflow, EditableWorkflowStatusItem,
+    DisplayMessageWorkflowStatusItem, WorkflowBackofficeFieldsFormDef,
+    ChoiceWorkflowStatusItem, JumpOnSubmitWorkflowStatusItem,
+    CommentableWorkflowStatusItem, WorkflowVariablesFieldsFormDef)
 from wcs.wf.backoffice_fields import SetBackofficeFieldsWorkflowStatusItem
 from wcs.wf.export_to_model import ExportToModel, transform_to_pdf
 from wcs.wf.jump import JumpWorkflowStatusItem
@@ -8714,750 +8711,3 @@ def test_structured_workflow_options(pub):
         '1_structured': {'id': '1', 'text': 'un', 'more': 'foo'},
     }
     assert '2020-04-18' in formdata.evolution[0].parts[0].content
-
-
-def test_block_simple(pub, blocks_feature):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test', type='string'),
-        fields.StringField(id='234', required=True, label='Test2', type='string'),
-    ]
-    block.store()
-
-    formdef = FormDef()
-    formdef.name = 'form title'
-    formdef.fields = [
-        fields.BlockField(id='1', label='test', type='block:foobar', hint='hintblock'),
-    ]
-    formdef.store()
-
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    assert resp.text.count('>hintblock<') == 1
-    resp.form['f1$element0$f123'] = 'foo'
-    resp.form['f1$element0$f234'] = 'bar'
-    resp = resp.form.submit('submit')  # -> validation page
-    assert resp.form['f1$element0$f123'].attrs['readonly']
-    assert resp.form['f1$element0$f123'].value == 'foo'
-    assert resp.form['f1$element0$f234'].attrs['readonly']
-    assert resp.form['f1$element0$f234'].value == 'bar'
-    resp = resp.form.submit('submit')  # -> end page
-    resp = resp.follow()
-    assert '>foo<' in resp
-    assert '>bar<' in resp
-
-
-def test_block_required(pub, blocks_feature):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test', type='string'),
-        fields.StringField(id='234', required=True, label='Test2', type='string'),
-    ]
-    block.store()
-
-    formdef = FormDef()
-    formdef.name = 'form title'
-    formdef.fields = [
-        fields.BlockField(id='1', label='test', type='block:foobar'),
-    ]
-    formdef.store()
-
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    resp = resp.form.submit('submit')  # -> error page
-    assert 'There were errors processing the form' in resp
-    assert resp.text.count('required field') == 1
-    resp.form['f1$element0$f123'] = 'foo'
-    resp.form['f1$element0$f234'] = 'bar'
-    resp = resp.form.submit('submit')  # -> validation page
-    assert 'Check values then click submit.' in resp.text
-
-    resp = app.get(formdef.get_url())
-    resp.form['f1$element0$f123'] = 'foo'
-    resp = resp.form.submit('submit')  # -> error page
-    assert 'There were errors processing the form' in resp
-    assert resp.text.count('required field') == 1
-    resp.form['f1$element0$f234'] = 'bar'
-    resp = resp.form.submit('submit')  # -> validation page
-    assert 'Check values then click submit.' in resp.text
-
-    # only one required
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test', type='string'),
-        fields.StringField(id='234', required=False, label='Test2', type='string'),
-    ]
-    block.store()
-
-    resp = app.get(formdef.get_url())
-    resp.form['f1$element0$f123'] = 'foo'
-    resp = resp.form.submit('submit')  # -> validation page
-    assert 'Check values then click submit.' in resp.text
-
-    # none required, but globally required
-    block.fields = [
-        fields.StringField(id='123', required=False, label='Test', type='string'),
-        fields.StringField(id='234', required=False, label='Test2', type='string'),
-    ]
-    block.store()
-
-    resp = app.get(formdef.get_url())
-    resp = resp.form.submit('submit')  # -> error page
-    assert 'There were errors processing the form' in resp
-    assert resp.text.count('required field') == 1
-    resp.form['f1$element0$f234'] = 'bar'
-    resp = resp.form.submit('submit')  # -> validation page
-    assert 'Check values then click submit.' in resp.text
-
-
-def test_block_date(pub, blocks_feature):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test', type='string'),
-        fields.DateField(id='234', required=True, label='Test2', type='date'),
-    ]
-    block.store()
-
-    formdef = FormDef()
-    formdef.name = 'form title'
-    formdef.fields = [
-        fields.BlockField(id='1', label='test', type='block:foobar'),
-    ]
-    formdef.store()
-
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    resp.form['f1$element0$f123'] = 'foo'
-    resp.form['f1$element0$f234'] = '2020-06-16'
-    resp = resp.form.submit('submit')  # -> validation page
-    assert 'Check values then click submit.' in resp.text
-    resp = resp.form.submit('submit')  # -> submit
-    resp = resp.follow()
-    assert '>2020-06-16<' in resp
-
-
-def test_block_bool(pub, blocks_feature):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test', type='string'),
-        fields.BoolField(id='234', required=False, label='Test2', type='bool'),
-    ]
-    block.store()
-
-    formdef = FormDef()
-    formdef.name = 'form title'
-    formdef.fields = [
-        fields.BlockField(id='1', label='test', type='block:foobar'),
-    ]
-    formdef.store()
-
-    for value in (True, False):
-        app = get_app(pub)
-        resp = app.get(formdef.get_url())
-        resp.form['f1$element0$f123'] = 'foo'
-        resp.form['f1$element0$f234'].checked = value
-        resp = resp.form.submit('submit')  # -> validation page
-        assert resp.form['f1$element0$f234disabled'].checked is value
-        assert resp.form['f1$element0$f234'].value == str(value)
-        assert 'Check values then click submit.' in resp
-        resp = resp.form.submit('submit')  # -> submit
-        resp = resp.follow()
-        if value:
-            assert '<div class="value">Yes</div>' in resp
-        else:
-            assert '<div class="value">No</div>' in resp
-
-
-def test_block_autocomplete_list(pub, blocks_feature):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test', type='string'),
-        fields.ItemField(id='234', required=True, label='Test2', type='item',
-            display_mode='autocomplete', items=['Foo', 'Bar']),
-    ]
-    block.store()
-
-    formdef = FormDef()
-    formdef.name = 'form title'
-    formdef.fields = [
-        fields.BlockField(id='1', label='test', type='block:foobar'),
-    ]
-    formdef.store()
-
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    resp.form['f1$element0$f123'] = 'foo'
-    resp.form['f1$element0$f234'] = 'Bar'
-    resp = resp.form.submit('submit')  # -> validation page
-    assert 'Check values then click submit.' in resp.text
-    resp = resp.form.submit('submit')  # -> submit
-    resp = resp.follow()
-    assert '>Bar<' in resp
-
-
-def test_block_geoloc_prefill(pub, blocks_feature):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test', type='string',
-            prefill={'type': 'geolocation', 'value': 'road'}),
-    ]
-    block.store()
-
-    formdef = FormDef()
-    formdef.name = 'form title'
-    formdef.fields = [
-        fields.BlockField(id='1', label='test', type='block:foobar'),
-    ]
-    formdef.store()
-
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    assert 'qommon.geolocation.js' in resp
-    assert resp.html.find('div', {'data-geolocation': 'road'})
-
-
-def test_block_string_prefill(pub, blocks_feature):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test', type='string',
-            prefill={'type': 'string', 'value': '{{ form_var_foo }} World'}),
-    ]
-    block.store()
-
-    formdef = FormDef()
-    formdef.name = 'form title'
-    formdef.fields = [
-        fields.PageField(id='0', label='1st page', type='page'),
-        fields.StringField(id='1', label='string', varname='foo'),
-        fields.PageField(id='2', label='2nd page', type='page'),
-        fields.BlockField(id='3', label='test', type='block:foobar'),
-    ]
-    formdef.store()
-    formdef.data_class().wipe()
-
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    resp.form['f1'] = 'Hello'
-    resp = resp.form.submit('submit')  # -> 2nd page
-    assert resp.form['f3$element0$f123'].value == 'Hello World'
-    resp = resp.form.submit('submit')  # -> validation page
-    resp = resp.form.submit('submit')  # -> end page
-    resp = resp.follow()
-
-    formdata = formdef.data_class().select()[0]
-    assert formdata.data['3']['data'][0]['123'] == 'Hello World'
-
-    # check unmodified prefilled field
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    resp.form['f1'] = 'Hello'
-    resp = resp.form.submit('submit')  # -> 2nd page
-    assert resp.form['f3$element0$f123'].value == 'Hello World'
-    resp = resp.form.submit('submit')  # -> validation page
-    resp = resp.form.submit('previous')  # -> 2nd page
-    resp = resp.form.submit('previous')  # -> 1st page
-    resp.form['f1'] = 'Test'
-    resp = resp.form.submit('submit')  # -> 2nd page
-    assert resp.form['f3$element0$f123'].value == 'Test World'
-
-    # check modified prefilled field
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    resp.form['f1'] = 'Hello'
-    resp = resp.form.submit('submit')  # -> 2nd page
-    assert resp.form['f3$element0$f123'].value == 'Hello World'
-    resp.form['f3$element0$f123'] = 'Foobar'
-    resp = resp.form.submit('submit')  # -> validation page
-    resp = resp.form.submit('previous')  # -> 2nd page
-    resp = resp.form.submit('previous')  # -> 1st page
-    resp.form['f1'] = 'Test'
-    resp = resp.form.submit('submit')  # -> 2nd page
-    assert resp.form['f3$element0$f123'].value == 'Foobar'
-
-
-def test_block_locked_prefill(pub, blocks_feature):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test', type='string',
-            prefill={'type': 'string', 'value': '{{ form_var_foo }} World', 'locked': True}),
-    ]
-    block.store()
-
-    formdef = FormDef()
-    formdef.name = 'form title'
-    formdef.fields = [
-        fields.PageField(id='0', label='1st page', type='page'),
-        fields.StringField(id='1', label='string', varname='foo'),
-        fields.PageField(id='2', label='2nd page', type='page'),
-        fields.BlockField(id='3', label='test', type='block:foobar'),
-    ]
-    formdef.store()
-    formdef.data_class().wipe()
-
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    resp.form['f1'] = 'Hello'
-    resp = resp.form.submit('submit')  # -> 2nd page
-    assert resp.form['f3$element0$f123'].value == 'Hello World'
-    assert 'readonly' in resp.form['f3$element0$f123'].attrs
-    resp.form['f3$element0$f123'].value = 'Hello'  # try changing the value
-    resp = resp.form.submit('submit')  # -> validation page
-    resp = resp.form.submit('submit')  # -> end page
-    resp = resp.follow()
-
-    formdata = formdef.data_class().select()[0]
-    assert formdata.data['3']['data'][0]['123'] == 'Hello World'  # value got reverted
-
-
-def test_workflow_form_block_prefill(pub):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test', type='string',
-            prefill={'type': 'user', 'value': 'email'}),
-    ]
-    block.store()
-
-    wf = Workflow(name='status')
-    st1 = wf.add_status('Status1', 'st1')
-
-    display_form = FormWorkflowStatusItem()
-    display_form.id = '_x'
-    display_form.by = ['_submitter']
-    display_form.varname = 'xxx'
-    display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
-    display_form.formdef.fields.append(
-        fields.BlockField(id='3', label='test', type='block:foobar')
-    )
-    st1.items.append(display_form)
-    display_form.parent = st1
-
-    wf.store()
-
-    formdef = create_formdef()
-    formdef.workflow_id = wf.id
-    formdef.fields = []
-    formdef.store()
-    formdef.data_class().wipe()
-
-    resp = login(get_app(pub), username='foo', password='foo').get('/test/')
-    resp = resp.form.submit('submit')  # -> validation
-    resp = resp.form.submit('submit').follow()
-    assert 'The form has been recorded' in resp
-    assert resp.form['f3$element0$f123'].value == 'foo@localhost'
-
-
-def test_block_title_and_comment(pub, blocks_feature):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test', type='string'),
-        fields.TitleField(id='234', label='Blah', type='title'),
-        fields.CommentField(id='345', label='Blah', type='comment'),
-    ]
-    block.store()
-
-    formdef = FormDef()
-    formdef.name = 'form title'
-    formdef.fields = [
-        fields.BlockField(id='1', label='test', type='block:foobar'),
-    ]
-    formdef.store()
-
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    resp.form['f1$element0$f123'] = 'foo'
-    resp = resp.form.submit('submit')  # -> validation page
-    assert 'Check values then click submit.' in resp.text
-    resp = resp.form.submit('submit')  # -> submit
-    resp = resp.follow()
-
-
-def test_block_multipage(pub, blocks_feature):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test', type='string'),
-        fields.StringField(id='234', required=True, label='Test2', type='string'),
-    ]
-    block.store()
-
-    formdef = FormDef()
-    formdef.name = 'form title'
-    formdef.fields = [
-        fields.PageField(id='0', label='1st page', type='page'),
-        fields.BlockField(id='1', label='test', type='block:foobar'),
-        fields.PageField(id='2', label='2nd page', type='page'),
-    ]
-    formdef.store()
-
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    resp.form['f1$element0$f123'] = 'foo'
-    resp.form['f1$element0$f234'] = 'bar'
-    resp = resp.form.submit('submit')  # -> 2nd page
-    resp = resp.form.submit('submit')  # -> validation page
-    assert resp.form['f1$element0$f123'].attrs['readonly']
-    assert resp.form['f1$element0$f123'].value == 'foo'
-    resp = resp.form.submit('previous')  # -> 2nd page
-    resp = resp.form.submit('previous')  # -> 1st page
-    assert 'readonly' not in resp.form['f1$element0$f123'].attrs
-    assert resp.form['f1$element0$f123'].value == 'foo'
-    resp = resp.form.submit('submit')  # -> 2nd page
-    resp = resp.form.submit('submit')  # -> validation page
-    resp = resp.form.submit('submit')  # -> submit
-    resp = resp.follow()
-    assert '>foo<' in resp
-    assert '>bar<' in resp
-
-
-def test_block_repeated(pub, blocks_feature):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test', type='string'),
-        fields.StringField(id='234', required=True, label='Test2', type='string'),
-    ]
-    block.store()
-
-    formdef = FormDef()
-    formdef.name = 'form title'
-    formdef.fields = [
-        fields.PageField(id='0', label='1st page', type='page'),
-        fields.BlockField(id='1', label='test', type='block:foobar', max_items=3, hint='hintblock'),
-        fields.PageField(id='2', label='2nd page', type='page'),
-    ]
-    formdef.store()
-
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    assert resp.text.count('>Test<') == 1
-    assert resp.text.count('>hintblock<') == 1
-    assert 'Add another' in resp
-    assert resp.html.find('div', {'class': 'list-add'})
-    resp = resp.form.submit('f1$add_element')
-    assert resp.text.count('>Test<') == 2
-    assert resp.text.count('>hintblock<') == 1
-    resp = resp.form.submit('f1$add_element')
-    assert resp.text.count('>Test<') == 3
-    assert resp.text.count('>hintblock<') == 1
-    assert 'Add another' not in resp
-
-    # fill items (1st and 3rd row)
-    resp.form['f1$element0$f123'] = 'foo'
-    resp.form['f1$element0$f234'] = 'bar'
-    resp.form['f1$element2$f123'] = 'foo2'
-    resp.form['f1$element2$f234'] = 'bar2'
-
-    resp = resp.form.submit('submit')  # -> 2nd page
-    resp = resp.form.submit('submit')  # -> validation page
-    assert 'Check values then click submit.' in resp.text
-    assert resp.form['f1$element0$f123'].value == 'foo'
-    assert resp.form['f1$element0$f234'].value == 'bar'
-    assert resp.form['f1$element1$f123'].value == 'foo2'
-    assert resp.form['f1$element1$f234'].value == 'bar2'
-
-    resp = resp.form.submit('previous')  # -> 2nd page
-    resp = resp.form.submit('previous')  # -> 1st page
-    assert 'readonly' not in resp.form['f1$element0$f123'].attrs
-    assert resp.form['f1$element0$f123'].value == 'foo'
-
-    resp = resp.form.submit('submit')  # -> 2nd page
-    resp = resp.form.submit('submit')  # -> validation page
-    resp = resp.form.submit('submit')  # -> submit
-    resp = resp.follow()
-    assert '>foo<' in resp
-    assert '>bar<' in resp
-    assert '>foo2<' in resp
-    assert '>bar2<' in resp
-
-
-def test_block_repeated_over_limit(pub, blocks_feature):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test', type='string'),
-        fields.StringField(id='234', required=True, label='Test2', type='string'),
-    ]
-    block.store()
-
-    formdef = FormDef()
-    formdef.name = 'form title'
-    formdef.fields = [
-        fields.PageField(id='0', label='1st page', type='page'),
-        fields.BlockField(id='1', label='test', type='block:foobar', max_items=3),
-        fields.PageField(id='2', label='2nd page', type='page'),
-    ]
-    formdef.store()
-
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    assert resp.text.count('>Test<') == 1
-    assert 'Add another' in resp
-    resp = resp.form.submit('f1$add_element')
-    assert resp.text.count('>Test<') == 2
-    resp = resp.form.submit('f1$add_element')
-    assert resp.text.count('>Test<') == 3
-    assert 'Add another' not in resp
-
-    # fill items
-    resp.form['f1$element0$f123'] = 'foo'
-    resp.form['f1$element0$f234'] = 'bar'
-    resp.form['f1$element1$f123'] = 'foo1'
-    resp.form['f1$element1$f234'] = 'bar1'
-    resp.form['f1$element2$f123'] = 'foo2'
-    resp.form['f1$element2$f234'] = 'bar2'
-
-    # (modify formdef to only allow 2)
-    formdef.fields[1].max_items = 2
-    formdef.store()
-
-    # submit form
-    resp = resp.form.submit('submit')
-    assert 'Too many elements (maximum: 2)' in resp
-
-
-def test_block_repeated_files(pub, blocks_feature):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test', type='string'),
-        fields.FileField(id='234', required=True, label='Test2', type='file'),
-    ]
-    block.store()
-
-    formdef = FormDef()
-    formdef.name = 'form title'
-    formdef.fields = [
-        fields.PageField(id='0', label='1st page', type='page'),
-        fields.BlockField(id='1', label='test', type='block:foobar', max_items=3),
-        fields.PageField(id='2', label='2nd page', type='page'),
-    ]
-    formdef.store()
-
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    assert resp.text.count('>Test<') == 1
-    assert 'Add another' in resp
-    resp = resp.form.submit('f1$add_element')
-    assert resp.text.count('>Test<') == 2
-    resp = resp.form.submit('f1$add_element')
-    assert resp.text.count('>Test<') == 3
-    assert 'Add another' not in resp
-
-    # fill items (1st and 3rd row)
-    resp.form['f1$element0$f123'] = 'foo'
-    resp.form['f1$element0$f234$file'] = Upload('test1.txt', b'foobar1', 'text/plain')
-    resp.form['f1$element2$f123'] = 'foo2'
-    resp.form['f1$element2$f234$file'] = Upload('test2.txt', b'foobar2', 'text/plain')
-
-    resp = resp.form.submit('submit')  # -> 2nd page
-    resp = resp.form.submit('submit')  # -> validation page
-    assert 'Check values then click submit.' in resp.text
-    assert resp.form['f1$element0$f123'].value == 'foo'
-    assert 'test1.txt' in resp
-    assert resp.form['f1$element1$f123'].value == 'foo2'
-    assert 'test2.txt' in resp
-
-    resp = resp.form.submit('previous')  # -> 2nd page
-    resp = resp.form.submit('previous')  # -> 1st page
-    resp = resp.form.submit('submit')  # -> 2nd page
-    resp = resp.form.submit('submit')  # -> validation page
-    resp = resp.form.submit('submit')  # -> submit
-    resp = resp.follow()
-    assert '>foo<' in resp
-    assert 'test1.txt' in resp
-    assert '>foo2<' in resp
-    assert 'test2.txt' in resp
-
-
-def test_block_digest(pub, blocks_feature):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test',
-            type='string', varname='foo'),
-        fields.StringField(id='234', required=True, label='Test2',
-            type='string', varname='bar'),
-    ]
-    block.store()
-
-    formdef = FormDef()
-    formdef.name = 'form title'
-    formdef.fields = [
-        fields.BlockField(id='1', label='test', type='block:foobar', max_items=3),
-    ]
-    formdef.store()
-
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    resp.form['f1$element0$f123'] = 'foo'
-    resp.form['f1$element0$f234'] = 'bar'
-    resp = resp.form.submit('f1$add_element')
-    resp.form['f1$element1$f123'] = 'foo2'
-    resp.form['f1$element1$f234'] = 'bar2'
-
-    resp = resp.form.submit('submit')  # -> validation page
-    resp = resp.form.submit('submit')  # -> submit
-
-    assert formdef.data_class().select()[0].data['1']['data'] == [
-            {'123': 'foo', '234': 'bar'}, {'123': 'foo2', '234': 'bar2'}]
-    # by default it gets the type of object
-    assert formdef.data_class().select()[0].data['1_display'] == 'foobar, foobar'
-
-    # set a digest template
-    formdef.data_class().wipe()
-
-    block.digest_template = 'X{{foobar_var_foo}}Y'
-    block.store()
-
-    resp = app.get(formdef.get_url())
-    resp.form['f1$element0$f123'] = 'foo'
-    resp.form['f1$element0$f234'] = 'bar'
-    resp = resp.form.submit('f1$add_element')
-    resp.form['f1$element1$f123'] = 'foo2'
-    resp.form['f1$element1$f234'] = 'bar2'
-
-    resp = resp.form.submit('submit')  # -> validation page
-    resp = resp.form.submit('submit')  # -> submit
-    assert formdef.data_class().select()[0].data['1']['data'] == [
-            {'123': 'foo', '234': 'bar'}, {'123': 'foo2', '234': 'bar2'}]
-    assert formdef.data_class().select()[0].data['1_display'] == 'XfooY, Xfoo2Y'
-
-
-def test_block_digest_item(pub, blocks_feature):
-    create_user(pub)
-    FormDef.wipe()
-    BlockDef.wipe()
-    NamedDataSource.wipe()
-
-    # add a named data source
-    data_source = NamedDataSource(name='foobar')
-    data_source.data_source = {
-            'type': 'formula',
-            'value': repr([{'id': '1', 'text': 'un'},
-                           {'id': '2', 'text': 'deux'}])}
-    data_source.store()
-
-    block = BlockDef()
-    block.name = 'foobar'
-    block.fields = [
-        fields.StringField(id='123', required=True, label='Test',
-            type='string', varname='foo'),
-        fields.ItemField(id='234', required=True, label='Test2',
-            type='item', varname='bar', data_source={'type': 'foobar'}),
-    ]
-    block.store()
-
-    formdef = FormDef()
-    formdef.name = 'form title'
-    formdef.fields = [
-        fields.BlockField(id='1', label='test', type='block:foobar', max_items=3),
-    ]
-    formdef.store()
-
-    app = get_app(pub)
-    resp = app.get(formdef.get_url())
-    resp.form['f1$element0$f123'] = 'foo'
-    resp.form['f1$element0$f234'] = '1'
-    resp = resp.form.submit('f1$add_element')
-    resp.form['f1$element1$f123'] = 'foo2'
-    resp.form['f1$element1$f234'] = '2'
-
-    resp = resp.form.submit('submit')  # -> validation page
-    resp = resp.form.submit('submit')  # -> submit
-
-    assert formdef.data_class().select()[0].data['1']['data'] == [
-            {'123': 'foo', '234': '1', '234_display': 'un'},
-            {'123': 'foo2', '234': '2', '234_display': 'deux'},
-    ]
-    # by default it gets the type of object
-    assert formdef.data_class().select()[0].data['1_display'] == 'foobar, foobar'
-
-    # set a digest template
-    formdef.data_class().wipe()
-
-    block.digest_template = 'X{{foobar_var_bar}}Y'
-    block.store()
-
-    resp = app.get(formdef.get_url())
-    resp.form['f1$element0$f123'] = 'foo'
-    resp.form['f1$element0$f234'] = '1'
-    resp = resp.form.submit('f1$add_element')
-    resp.form['f1$element1$f123'] = 'foo2'
-    resp.form['f1$element1$f234'] = '2'
-
-    resp = resp.form.submit('submit')  # -> validation page
-    resp = resp.form.submit('submit')  # -> submit
-    assert formdef.data_class().select()[0].data['1']['data'] == [
-            {'123': 'foo', '234': '1', '234_display': 'un'},
-            {'123': 'foo2', '234': '2', '234_display': 'deux'},
-    ]
-    assert formdef.data_class().select()[0].data['1_display'] == 'XunY, XdeuxY'
diff --git a/tests/test_form_pages/test_block.py b/tests/test_form_pages/test_block.py
new file mode 100644
index 00000000..c040e4ab
--- /dev/null
+++ b/tests/test_form_pages/test_block.py
@@ -0,0 +1,802 @@
+# -*- coding: utf-8 -*-
+
+import pytest
+from webtest import Upload
+
+from wcs.qommon.ident.password_accounts import PasswordAccount
+from wcs.blocks import BlockDef
+from wcs.formdef import FormDef
+from wcs.workflows import Workflow
+from wcs.wf.form import FormWorkflowStatusItem, WorkflowFormFieldsFormDef
+from wcs.categories import Category
+from wcs.data_sources import NamedDataSource
+from wcs import fields
+
+from utilities import get_app, login, create_temporary_pub, clean_temporary_pub
+
+
+def pytest_generate_tests(metafunc):
+    if 'pub' in metafunc.fixturenames:
+        metafunc.parametrize('pub', ['pickle', 'sql', 'pickle-templates', 'pickle-lazy'], indirect=True)
+
+
+@pytest.fixture
+def pub(request, emails):
+    pub = create_temporary_pub(
+            sql_mode=bool('sql' in request.param),
+            templates_mode=bool('templates' in request.param),
+            lazy_mode=bool('lazy' in request.param),
+            )
+    pub.cfg['identification'] = {'methods': ['password']}
+    pub.cfg['language'] = {'language': 'en'}
+    pub.write_cfg()
+
+    if Category.count() == 0:
+        cat = Category(name='foobar')
+        cat.store()
+
+    return pub
+
+
+def teardown_module(module):
+    clean_temporary_pub()
+
+
+def create_user(pub):
+    pub.user_class.wipe()
+    PasswordAccount.wipe()
+
+    user = pub.user_class()
+    user.name = 'User Name'
+    user.email = 'foo@localhost'
+    user.store()
+    account = PasswordAccount(id='foo')
+    account.set_password('foo')
+    account.user_id = user.id
+    account.store()
+    return user
+
+
+def test_block_simple(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(id='123', required=True, label='Test', type='string'),
+        fields.StringField(id='234', required=True, label='Test2', type='string'),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.BlockField(id='1', label='test', type='block:foobar', hint='hintblock'),
+    ]
+    formdef.store()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    assert resp.text.count('>hintblock<') == 1
+    resp.form['f1$element0$f123'] = 'foo'
+    resp.form['f1$element0$f234'] = 'bar'
+    resp = resp.form.submit('submit')  # -> validation page
+    assert resp.form['f1$element0$f123'].attrs['readonly']
+    assert resp.form['f1$element0$f123'].value == 'foo'
+    assert resp.form['f1$element0$f234'].attrs['readonly']
+    assert resp.form['f1$element0$f234'].value == 'bar'
+    resp = resp.form.submit('submit')  # -> end page
+    resp = resp.follow()
+    assert '>foo<' in resp
+    assert '>bar<' in resp
+
+
+def test_block_required(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(id='123', required=True, label='Test', type='string'),
+        fields.StringField(id='234', required=True, label='Test2', type='string'),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.BlockField(id='1', label='test', type='block:foobar'),
+    ]
+    formdef.store()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    resp = resp.form.submit('submit')  # -> error page
+    assert 'There were errors processing the form' in resp
+    assert resp.text.count('required field') == 1
+    resp.form['f1$element0$f123'] = 'foo'
+    resp.form['f1$element0$f234'] = 'bar'
+    resp = resp.form.submit('submit')  # -> validation page
+    assert 'Check values then click submit.' in resp.text
+
+    resp = app.get(formdef.get_url())
+    resp.form['f1$element0$f123'] = 'foo'
+    resp = resp.form.submit('submit')  # -> error page
+    assert 'There were errors processing the form' in resp
+    assert resp.text.count('required field') == 1
+    resp.form['f1$element0$f234'] = 'bar'
+    resp = resp.form.submit('submit')  # -> validation page
+    assert 'Check values then click submit.' in resp.text
+
+    # only one required
+    block.fields = [
+        fields.StringField(id='123', required=True, label='Test', type='string'),
+        fields.StringField(id='234', required=False, label='Test2', type='string'),
+    ]
+    block.store()
+
+    resp = app.get(formdef.get_url())
+    resp.form['f1$element0$f123'] = 'foo'
+    resp = resp.form.submit('submit')  # -> validation page
+    assert 'Check values then click submit.' in resp.text
+
+    # none required, but globally required
+    block.fields = [
+        fields.StringField(id='123', required=False, label='Test', type='string'),
+        fields.StringField(id='234', required=False, label='Test2', type='string'),
+    ]
+    block.store()
+
+    resp = app.get(formdef.get_url())
+    resp = resp.form.submit('submit')  # -> error page
+    assert 'There were errors processing the form' in resp
+    assert resp.text.count('required field') == 1
+    resp.form['f1$element0$f234'] = 'bar'
+    resp = resp.form.submit('submit')  # -> validation page
+    assert 'Check values then click submit.' in resp.text
+
+
+def test_block_date(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(id='123', required=True, label='Test', type='string'),
+        fields.DateField(id='234', required=True, label='Test2', type='date'),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.BlockField(id='1', label='test', type='block:foobar'),
+    ]
+    formdef.store()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    resp.form['f1$element0$f123'] = 'foo'
+    resp.form['f1$element0$f234'] = '2020-06-16'
+    resp = resp.form.submit('submit')  # -> validation page
+    assert 'Check values then click submit.' in resp.text
+    resp = resp.form.submit('submit')  # -> submit
+    resp = resp.follow()
+    assert '>2020-06-16<' in resp
+
+
+def test_block_bool(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(id='123', required=True, label='Test', type='string'),
+        fields.BoolField(id='234', required=False, label='Test2', type='bool'),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.BlockField(id='1', label='test', type='block:foobar'),
+    ]
+    formdef.store()
+
+    for value in (True, False):
+        app = get_app(pub)
+        resp = app.get(formdef.get_url())
+        resp.form['f1$element0$f123'] = 'foo'
+        resp.form['f1$element0$f234'].checked = value
+        resp = resp.form.submit('submit')  # -> validation page
+        assert resp.form['f1$element0$f234disabled'].checked is value
+        assert resp.form['f1$element0$f234'].value == str(value)
+        assert 'Check values then click submit.' in resp
+        resp = resp.form.submit('submit')  # -> submit
+        resp = resp.follow()
+        if value:
+            assert '<div class="value">Yes</div>' in resp
+        else:
+            assert '<div class="value">No</div>' in resp
+
+
+def test_block_autocomplete_list(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(id='123', required=True, label='Test', type='string'),
+        fields.ItemField(
+            id='234', required=True, label='Test2', type='item',
+            display_mode='autocomplete', items=['Foo', 'Bar']),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.BlockField(id='1', label='test', type='block:foobar'),
+    ]
+    formdef.store()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    resp.form['f1$element0$f123'] = 'foo'
+    resp.form['f1$element0$f234'] = 'Bar'
+    resp = resp.form.submit('submit')  # -> validation page
+    assert 'Check values then click submit.' in resp.text
+    resp = resp.form.submit('submit')  # -> submit
+    resp = resp.follow()
+    assert '>Bar<' in resp
+
+
+def test_block_geoloc_prefill(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(
+            id='123', required=True, label='Test', type='string',
+            prefill={'type': 'geolocation', 'value': 'road'}),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.BlockField(id='1', label='test', type='block:foobar'),
+    ]
+    formdef.store()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    assert 'qommon.geolocation.js' in resp
+    assert resp.html.find('div', {'data-geolocation': 'road'})
+
+
+def test_block_string_prefill(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(
+            id='123', required=True, label='Test', type='string',
+            prefill={'type': 'string', 'value': '{{ form_var_foo }} World'}),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.PageField(id='0', label='1st page', type='page'),
+        fields.StringField(id='1', label='string', varname='foo'),
+        fields.PageField(id='2', label='2nd page', type='page'),
+        fields.BlockField(id='3', label='test', type='block:foobar'),
+    ]
+    formdef.store()
+    formdef.data_class().wipe()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    resp.form['f1'] = 'Hello'
+    resp = resp.form.submit('submit')  # -> 2nd page
+    assert resp.form['f3$element0$f123'].value == 'Hello World'
+    resp = resp.form.submit('submit')  # -> validation page
+    resp = resp.form.submit('submit')  # -> end page
+    resp = resp.follow()
+
+    formdata = formdef.data_class().select()[0]
+    assert formdata.data['3']['data'][0]['123'] == 'Hello World'
+
+    # check unmodified prefilled field
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    resp.form['f1'] = 'Hello'
+    resp = resp.form.submit('submit')  # -> 2nd page
+    assert resp.form['f3$element0$f123'].value == 'Hello World'
+    resp = resp.form.submit('submit')  # -> validation page
+    resp = resp.form.submit('previous')  # -> 2nd page
+    resp = resp.form.submit('previous')  # -> 1st page
+    resp.form['f1'] = 'Test'
+    resp = resp.form.submit('submit')  # -> 2nd page
+    assert resp.form['f3$element0$f123'].value == 'Test World'
+
+    # check modified prefilled field
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    resp.form['f1'] = 'Hello'
+    resp = resp.form.submit('submit')  # -> 2nd page
+    assert resp.form['f3$element0$f123'].value == 'Hello World'
+    resp.form['f3$element0$f123'] = 'Foobar'
+    resp = resp.form.submit('submit')  # -> validation page
+    resp = resp.form.submit('previous')  # -> 2nd page
+    resp = resp.form.submit('previous')  # -> 1st page
+    resp.form['f1'] = 'Test'
+    resp = resp.form.submit('submit')  # -> 2nd page
+    assert resp.form['f3$element0$f123'].value == 'Foobar'
+
+
+def test_block_locked_prefill(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(
+            id='123', required=True, label='Test', type='string',
+            prefill={'type': 'string', 'value': '{{ form_var_foo }} World', 'locked': True}),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.PageField(id='0', label='1st page', type='page'),
+        fields.StringField(id='1', label='string', varname='foo'),
+        fields.PageField(id='2', label='2nd page', type='page'),
+        fields.BlockField(id='3', label='test', type='block:foobar'),
+    ]
+    formdef.store()
+    formdef.data_class().wipe()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    resp.form['f1'] = 'Hello'
+    resp = resp.form.submit('submit')  # -> 2nd page
+    assert resp.form['f3$element0$f123'].value == 'Hello World'
+    assert 'readonly' in resp.form['f3$element0$f123'].attrs
+    resp.form['f3$element0$f123'].value = 'Hello'  # try changing the value
+    resp = resp.form.submit('submit')  # -> validation page
+    resp = resp.form.submit('submit')  # -> end page
+    resp = resp.follow()
+
+    formdata = formdef.data_class().select()[0]
+    assert formdata.data['3']['data'][0]['123'] == 'Hello World'  # value got reverted
+
+
+def test_workflow_form_block_prefill(pub):
+    FormDef.wipe()
+    BlockDef.wipe()
+    create_user(pub)
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(
+            id='123', required=True, label='Test', type='string',
+            prefill={'type': 'user', 'value': 'email'}),
+    ]
+    block.store()
+
+    wf = Workflow(name='status')
+    st1 = wf.add_status('Status1', 'st1')
+
+    display_form = FormWorkflowStatusItem()
+    display_form.id = '_x'
+    display_form.by = ['_submitter']
+    display_form.varname = 'xxx'
+    display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
+    display_form.formdef.fields.append(
+        fields.BlockField(id='3', label='test', type='block:foobar')
+    )
+    st1.items.append(display_form)
+    display_form.parent = st1
+
+    wf.store()
+
+    FormDef.wipe()
+    formdef = FormDef()
+    formdef.name = 'test'
+    formdef.fields = []
+    formdef.workflow_id = wf.id
+    formdef.store()
+    formdef.data_class().wipe()
+
+    resp = login(get_app(pub), username='foo', password='foo').get('/test/')
+    resp = resp.form.submit('submit')  # -> validation
+    resp = resp.form.submit('submit').follow()
+    assert 'The form has been recorded' in resp
+    assert resp.form['f3$element0$f123'].value == 'foo@localhost'
+
+
+def test_block_title_and_comment(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(id='123', required=True, label='Test', type='string'),
+        fields.TitleField(id='234', label='Blah', type='title'),
+        fields.CommentField(id='345', label='Blah', type='comment'),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.BlockField(id='1', label='test', type='block:foobar'),
+    ]
+    formdef.store()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    resp.form['f1$element0$f123'] = 'foo'
+    resp = resp.form.submit('submit')  # -> validation page
+    assert 'Check values then click submit.' in resp.text
+    resp = resp.form.submit('submit')  # -> submit
+    resp = resp.follow()
+
+
+def test_block_multipage(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(id='123', required=True, label='Test', type='string'),
+        fields.StringField(id='234', required=True, label='Test2', type='string'),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.PageField(id='0', label='1st page', type='page'),
+        fields.BlockField(id='1', label='test', type='block:foobar'),
+        fields.PageField(id='2', label='2nd page', type='page'),
+    ]
+    formdef.store()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    resp.form['f1$element0$f123'] = 'foo'
+    resp.form['f1$element0$f234'] = 'bar'
+    resp = resp.form.submit('submit')  # -> 2nd page
+    resp = resp.form.submit('submit')  # -> validation page
+    assert resp.form['f1$element0$f123'].attrs['readonly']
+    assert resp.form['f1$element0$f123'].value == 'foo'
+    resp = resp.form.submit('previous')  # -> 2nd page
+    resp = resp.form.submit('previous')  # -> 1st page
+    assert 'readonly' not in resp.form['f1$element0$f123'].attrs
+    assert resp.form['f1$element0$f123'].value == 'foo'
+    resp = resp.form.submit('submit')  # -> 2nd page
+    resp = resp.form.submit('submit')  # -> validation page
+    resp = resp.form.submit('submit')  # -> submit
+    resp = resp.follow()
+    assert '>foo<' in resp
+    assert '>bar<' in resp
+
+
+def test_block_repeated(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(id='123', required=True, label='Test', type='string'),
+        fields.StringField(id='234', required=True, label='Test2', type='string'),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.PageField(id='0', label='1st page', type='page'),
+        fields.BlockField(id='1', label='test', type='block:foobar', max_items=3, hint='hintblock'),
+        fields.PageField(id='2', label='2nd page', type='page'),
+    ]
+    formdef.store()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    assert resp.text.count('>Test<') == 1
+    assert resp.text.count('>hintblock<') == 1
+    assert 'Add another' in resp
+    assert resp.html.find('div', {'class': 'list-add'})
+    resp = resp.form.submit('f1$add_element')
+    assert resp.text.count('>Test<') == 2
+    assert resp.text.count('>hintblock<') == 1
+    resp = resp.form.submit('f1$add_element')
+    assert resp.text.count('>Test<') == 3
+    assert resp.text.count('>hintblock<') == 1
+    assert 'Add another' not in resp
+
+    # fill items (1st and 3rd row)
+    resp.form['f1$element0$f123'] = 'foo'
+    resp.form['f1$element0$f234'] = 'bar'
+    resp.form['f1$element2$f123'] = 'foo2'
+    resp.form['f1$element2$f234'] = 'bar2'
+
+    resp = resp.form.submit('submit')  # -> 2nd page
+    resp = resp.form.submit('submit')  # -> validation page
+    assert 'Check values then click submit.' in resp.text
+    assert resp.form['f1$element0$f123'].value == 'foo'
+    assert resp.form['f1$element0$f234'].value == 'bar'
+    assert resp.form['f1$element1$f123'].value == 'foo2'
+    assert resp.form['f1$element1$f234'].value == 'bar2'
+
+    resp = resp.form.submit('previous')  # -> 2nd page
+    resp = resp.form.submit('previous')  # -> 1st page
+    assert 'readonly' not in resp.form['f1$element0$f123'].attrs
+    assert resp.form['f1$element0$f123'].value == 'foo'
+
+    resp = resp.form.submit('submit')  # -> 2nd page
+    resp = resp.form.submit('submit')  # -> validation page
+    resp = resp.form.submit('submit')  # -> submit
+    resp = resp.follow()
+    assert '>foo<' in resp
+    assert '>bar<' in resp
+    assert '>foo2<' in resp
+    assert '>bar2<' in resp
+
+
+def test_block_repeated_over_limit(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(id='123', required=True, label='Test', type='string'),
+        fields.StringField(id='234', required=True, label='Test2', type='string'),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.PageField(id='0', label='1st page', type='page'),
+        fields.BlockField(id='1', label='test', type='block:foobar', max_items=3),
+        fields.PageField(id='2', label='2nd page', type='page'),
+    ]
+    formdef.store()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    assert resp.text.count('>Test<') == 1
+    assert 'Add another' in resp
+    resp = resp.form.submit('f1$add_element')
+    assert resp.text.count('>Test<') == 2
+    resp = resp.form.submit('f1$add_element')
+    assert resp.text.count('>Test<') == 3
+    assert 'Add another' not in resp
+
+    # fill items
+    resp.form['f1$element0$f123'] = 'foo'
+    resp.form['f1$element0$f234'] = 'bar'
+    resp.form['f1$element1$f123'] = 'foo1'
+    resp.form['f1$element1$f234'] = 'bar1'
+    resp.form['f1$element2$f123'] = 'foo2'
+    resp.form['f1$element2$f234'] = 'bar2'
+
+    # (modify formdef to only allow 2)
+    formdef.fields[1].max_items = 2
+    formdef.store()
+
+    # submit form
+    resp = resp.form.submit('submit')
+    assert 'Too many elements (maximum: 2)' in resp
+
+
+def test_block_repeated_files(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(id='123', required=True, label='Test', type='string'),
+        fields.FileField(id='234', required=True, label='Test2', type='file'),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.PageField(id='0', label='1st page', type='page'),
+        fields.BlockField(id='1', label='test', type='block:foobar', max_items=3),
+        fields.PageField(id='2', label='2nd page', type='page'),
+    ]
+    formdef.store()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    assert resp.text.count('>Test<') == 1
+    assert 'Add another' in resp
+    resp = resp.form.submit('f1$add_element')
+    assert resp.text.count('>Test<') == 2
+    resp = resp.form.submit('f1$add_element')
+    assert resp.text.count('>Test<') == 3
+    assert 'Add another' not in resp
+
+    # fill items (1st and 3rd row)
+    resp.form['f1$element0$f123'] = 'foo'
+    resp.form['f1$element0$f234$file'] = Upload('test1.txt', b'foobar1', 'text/plain')
+    resp.form['f1$element2$f123'] = 'foo2'
+    resp.form['f1$element2$f234$file'] = Upload('test2.txt', b'foobar2', 'text/plain')
+
+    resp = resp.form.submit('submit')  # -> 2nd page
+    resp = resp.form.submit('submit')  # -> validation page
+    assert 'Check values then click submit.' in resp.text
+    assert resp.form['f1$element0$f123'].value == 'foo'
+    assert 'test1.txt' in resp
+    assert resp.form['f1$element1$f123'].value == 'foo2'
+    assert 'test2.txt' in resp
+
+    resp = resp.form.submit('previous')  # -> 2nd page
+    resp = resp.form.submit('previous')  # -> 1st page
+    resp = resp.form.submit('submit')  # -> 2nd page
+    resp = resp.form.submit('submit')  # -> validation page
+    resp = resp.form.submit('submit')  # -> submit
+    resp = resp.follow()
+    assert '>foo<' in resp
+    assert 'test1.txt' in resp
+    assert '>foo2<' in resp
+    assert 'test2.txt' in resp
+
+
+def test_block_digest(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(
+            id='123', required=True, label='Test',
+            type='string', varname='foo'),
+        fields.StringField(
+            id='234', required=True, label='Test2',
+            type='string', varname='bar'),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.BlockField(id='1', label='test', type='block:foobar', max_items=3),
+    ]
+    formdef.store()
+    formdef.data_class().wipe()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    resp.form['f1$element0$f123'] = 'foo'
+    resp.form['f1$element0$f234'] = 'bar'
+    resp = resp.form.submit('f1$add_element')
+    resp.form['f1$element1$f123'] = 'foo2'
+    resp.form['f1$element1$f234'] = 'bar2'
+
+    resp = resp.form.submit('submit')  # -> validation page
+    resp = resp.form.submit('submit')  # -> submit
+
+    assert formdef.data_class().select()[0].data['1']['data'] == [
+            {'123': 'foo', '234': 'bar'}, {'123': 'foo2', '234': 'bar2'}]
+    # by default it gets the type of object
+    assert formdef.data_class().select()[0].data['1_display'] == 'foobar, foobar'
+
+    # set a digest template
+    formdef.data_class().wipe()
+
+    block.digest_template = 'X{{foobar_var_foo}}Y'
+    block.store()
+
+    resp = app.get(formdef.get_url())
+    resp.form['f1$element0$f123'] = 'foo'
+    resp.form['f1$element0$f234'] = 'bar'
+    resp = resp.form.submit('f1$add_element')
+    resp.form['f1$element1$f123'] = 'foo2'
+    resp.form['f1$element1$f234'] = 'bar2'
+
+    resp = resp.form.submit('submit')  # -> validation page
+    resp = resp.form.submit('submit')  # -> submit
+    assert formdef.data_class().select()[0].data['1']['data'] == [
+            {'123': 'foo', '234': 'bar'}, {'123': 'foo2', '234': 'bar2'}]
+    assert formdef.data_class().select()[0].data['1_display'] == 'XfooY, Xfoo2Y'
+
+
+def test_block_digest_item(pub, blocks_feature):
+    FormDef.wipe()
+    BlockDef.wipe()
+    NamedDataSource.wipe()
+
+    # add a named data source
+    data_source = NamedDataSource(name='foobar')
+    data_source.data_source = {
+            'type': 'formula',
+            'value': repr([{'id': '1', 'text': 'un'},
+                           {'id': '2', 'text': 'deux'}])}
+    data_source.store()
+
+    block = BlockDef()
+    block.name = 'foobar'
+    block.fields = [
+        fields.StringField(
+            id='123', required=True, label='Test',
+            type='string', varname='foo'),
+        fields.ItemField(
+            id='234', required=True, label='Test2',
+            type='item', varname='bar', data_source={'type': 'foobar'}),
+    ]
+    block.store()
+
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.BlockField(id='1', label='test', type='block:foobar', max_items=3),
+    ]
+    formdef.store()
+    formdef.data_class().wipe()
+
+    app = get_app(pub)
+    resp = app.get(formdef.get_url())
+    resp.form['f1$element0$f123'] = 'foo'
+    resp.form['f1$element0$f234'] = '1'
+    resp = resp.form.submit('f1$add_element')
+    resp.form['f1$element1$f123'] = 'foo2'
+    resp.form['f1$element1$f234'] = '2'
+
+    resp = resp.form.submit('submit')  # -> validation page
+    resp = resp.form.submit('submit')  # -> submit
+
+    assert formdef.data_class().select()[0].data['1']['data'] == [
+            {'123': 'foo', '234': '1', '234_display': 'un'},
+            {'123': 'foo2', '234': '2', '234_display': 'deux'},
+    ]
+    # by default it gets the type of object
+    assert formdef.data_class().select()[0].data['1_display'] == 'foobar, foobar'
+
+    # set a digest template
+    formdef.data_class().wipe()
+
+    block.digest_template = 'X{{foobar_var_bar}}Y'
+    block.store()
+
+    resp = app.get(formdef.get_url())
+    resp.form['f1$element0$f123'] = 'foo'
+    resp.form['f1$element0$f234'] = '1'
+    resp = resp.form.submit('f1$add_element')
+    resp.form['f1$element1$f123'] = 'foo2'
+    resp.form['f1$element1$f234'] = '2'
+
+    resp = resp.form.submit('submit')  # -> validation page
+    resp = resp.form.submit('submit')  # -> submit
+    assert formdef.data_class().select()[0].data['1']['data'] == [
+            {'123': 'foo', '234': '1', '234_display': 'un'},
+            {'123': 'foo2', '234': '2', '234_display': 'deux'},
+    ]
+    assert formdef.data_class().select()[0].data['1_display'] == 'XunY, XdeuxY'
-- 
2.28.0

