From 3fe1a6777c5bcc0638ce99cecb2ff28f8f166fb7 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Sat, 18 Sep 2021 18:32:53 +0200
Subject: [PATCH] tests: add tests for dynamic custom views lookup based on
 datasource (#48556)

---
 tests/form_pages/test_all.py | 274 +++++++++++++++++++++++++++++++++++
 1 file changed, 274 insertions(+)

diff --git a/tests/form_pages/test_all.py b/tests/form_pages/test_all.py
index 51f4fbf70..5bd54f9a5 100644
--- a/tests/form_pages/test_all.py
+++ b/tests/form_pages/test_all.py
@@ -5571,16 +5571,290 @@ def test_dynamic_item_field_from_custom_view_on_cards(pub):
     assert resp.form['f1'].options == []
     if pub.is_using_postgresql():
         assert pub.loggederror_class.count() == 1
         logged_error = pub.loggederror_class.select()[0]
         assert logged_error.formdef_id == formdef.id
         assert logged_error.summary == '[DATASOURCE] Unknown custom view "as-data-source" for CardDef "items"'
 
 
+def test_dynamic_item_field_from_custom_view_on_cards_lookup_by_key(pub):
+    if not pub.is_using_postgresql():
+        pytest.skip('this requires SQL')
+        return
+
+    pub.role_class.wipe()
+    pub.custom_view_class.wipe()
+
+    user = create_user(pub)
+    role = pub.role_class(name='xxx')
+    role.store()
+    user.roles = [role.id]
+    user.is_admin = True
+    user.store()
+
+    formdef = create_formdef()
+    formdef.data_class().wipe()
+
+    data_source = {
+        'type': 'formula',
+        'value': repr(
+            [
+                {'id': '0', 'text': 'foo'},
+                {'id': '1', 'text': 'bar'},
+                {'id': '2', 'text': 'baz'},
+            ]
+        ),
+    }
+
+    CardDef.wipe()
+    carddef = CardDef()
+    carddef.name = 'items'
+    carddef.digest_templates = {'default': '{{form_var_attr}}'}
+    carddef.workflow_roles = {'_editor': user.roles[0]}
+    carddef.fields = [
+        fields.ItemField(id='0', type='item', label='item', data_source=data_source),
+        fields.StringField(id='1', type='string', label='string', varname='attr'),
+    ]
+    carddef.store()
+    carddef.data_class().wipe()
+    baz_ids = set()
+    for key, value in enumerate(['foo', 'bar', 'baz']):
+        for i in range(0, 10):
+            carddata = carddef.data_class()()
+            carddata.data = {
+                '0': repr(key),
+                '0_display': value,
+                '1': 'attr%s%s' % (key, i),
+            }
+            carddata.just_created()
+            carddata.store()
+            if value == 'baz':
+                baz_ids.add(str(carddata.id))
+
+    # create custom view
+    app = login(get_app(pub), username='foo', password='foo')
+
+    resp = app.get('/backoffice/data/items/')
+    if pub.is_using_postgresql():
+        assert resp.text.count('<tr') == 21  # thead + 20 items (max per page)
+    else:
+        assert resp.text.count('<tr') == 31  # thead + all items
+    resp.forms['listing-settings']['filter-0'].checked = True
+    resp.forms['listing-settings']['filter-status'].checked = True
+    resp = resp.forms['listing-settings'].submit()
+
+    resp.forms['listing-settings']['filter'].value = 'recorded'
+    resp = resp.forms['listing-settings'].submit()
+
+    resp.forms['save-custom-view']['title'] = 'as data source'
+    resp.forms['save-custom-view']['visibility'] = 'datasource'
+    resp = resp.forms['save-custom-view'].submit().follow()
+
+    assert resp.forms['listing-settings']['filter-0-value'].attrs['data-allow-template']
+    assert 'custom value' in [x[2] for x in resp.forms['listing-settings']['filter-0-value'].options]
+    resp.forms['listing-settings']['filter-0-value'].force_value('{{ form_var_blah_raw }}')
+
+    resp = resp.forms['listing-settings'].submit()
+    assert resp.forms['listing-settings']['filter-0-value'].value == '{{ form_var_blah_raw }}'
+    assert resp.text.count('<tr') == 1  # thead only
+
+    # save custom view with filter
+    resp = resp.forms['save-custom-view'].submit().follow()
+
+    custom_view = pub.custom_view_class.select()[0]
+
+    # use custom view as source
+    ds = {'type': 'carddef:%s:%s' % (carddef.url_name, custom_view.slug)}
+    formdef.fields = [
+        fields.ItemField(id='0', type='item', label='item', varname='blah', data_source=data_source),
+        fields.ItemField(id='1', label='string', type='item', data_source=ds, display_disabled_items=True),
+    ]
+    formdef.store()
+
+    resp = get_app(pub).get('/test/')
+    assert resp.form['f1'].options == [('', False, '---')]
+    resp.form['f0'] = '2'
+    live_resp = app.post('/test/live?modified_field_id=0', params=resp.form.submit_fields())
+    assert len(live_resp.json['result']['1']['items']) == 10
+    assert {x['id'] for x in live_resp.json['result']['1']['items']} == baz_ids
+
+    resp.form['f1'].options = []
+    for item in live_resp.json['result']['1']['items']:
+        # simulate javascript filling the <select>
+        resp.form['f1'].options.append((item['id'], False, item['text']))
+
+    resp.form['f1'] = resp.form['f1'].options[0][0]
+    resp = resp.form.submit('submit')  # -> validation page
+    resp = resp.form.submit('submit')  # -> submit
+    assert formdef.data_class().select()[0].data['1'] in baz_ids
+
+    # same in autocomplete mode
+    formdef.fields[1].display_mode = 'autocomplete'
+    formdef.store()
+    app = get_app(pub)
+    resp = app.get('/test/')
+    # simulate select2 mode, with qommon.forms.js adding an extra hidden widget
+    resp.form.fields['f1_display'] = Hidden(form=resp.form, tag='input', name='f1_display', pos=10)
+    select2_url = resp.pyquery('select:last').attr['data-select2-url']
+    resp_json = app.get(select2_url + '?q=')
+    assert len(resp_json.json['data']) == 0
+    resp.form['f0'] = '2'
+
+    live_resp = app.post('/test/live?modified_field_id=0', params=resp.form.submit_fields())
+    new_select2_url = live_resp.json['result']['1']['source_url']
+    resp_json = app.get(new_select2_url + '?q=')
+    assert len(resp_json.json['data']) == 10
+    assert {str(x['id']) for x in resp_json.json['data']} == baz_ids
+
+    resp.form['f1'].force_value(str(resp_json.json['data'][0]['id']))
+    resp.form.fields['f1_display'].force_value(resp_json.json['data'][0]['text'])
+
+    resp = resp.form.submit('submit')  # -> validation page
+    resp = resp.form.submit('submit')  # -> submit
+    assert formdef.data_class().select()[0].data['1'] in baz_ids
+
+
+def test_dynamic_item_field_from_custom_view_on_cards_lookup_by_value(pub):
+    if not pub.is_using_postgresql():
+        pytest.skip('this requires SQL')
+        return
+
+    pub.role_class.wipe()
+    pub.custom_view_class.wipe()
+
+    user = create_user(pub)
+    role = pub.role_class(name='xxx')
+    role.store()
+    user.roles = [role.id]
+    user.is_admin = True
+    user.store()
+
+    formdef = create_formdef()
+    formdef.data_class().wipe()
+
+    data_source = {
+        'type': 'formula',
+        'value': repr(
+            [
+                {'id': '0', 'text': 'foo'},
+                {'id': '1', 'text': 'bar'},
+                {'id': '2', 'text': 'baz'},
+            ]
+        ),
+    }
+
+    CardDef.wipe()
+    carddef = CardDef()
+    carddef.name = 'items'
+    carddef.digest_templates = {'default': '{{form_var_attr}}'}
+    carddef.workflow_roles = {'_editor': user.roles[0]}
+    carddef.fields = [
+        fields.ItemField(id='0', type='item', label='item', data_source=data_source),
+        fields.StringField(id='1', type='string', label='string', varname='attr'),
+    ]
+    carddef.store()
+    carddef.data_class().wipe()
+    baz_ids = set()
+    for key, value in enumerate(['foo', 'bar', 'baz']):
+        for i in range(0, 10):
+            carddata = carddef.data_class()()
+            carddata.data = {
+                '0': repr(key),
+                '0_display': value,
+                '1': 'attr%s%s' % (key, i),
+            }
+            carddata.just_created()
+            carddata.store()
+            if value == 'baz':
+                baz_ids.add(str(carddata.id))
+
+    # create custom view
+    app = login(get_app(pub), username='foo', password='foo')
+
+    resp = app.get('/backoffice/data/items/')
+    if pub.is_using_postgresql():
+        assert resp.text.count('<tr') == 21  # thead + 20 items (max per page)
+    else:
+        assert resp.text.count('<tr') == 31  # thead + all items
+    resp.forms['listing-settings']['filter-0'].checked = True
+    resp.forms['listing-settings']['filter-status'].checked = True
+    resp = resp.forms['listing-settings'].submit()
+
+    resp.forms['listing-settings']['filter'].value = 'recorded'
+    resp = resp.forms['listing-settings'].submit()
+
+    resp.forms['save-custom-view']['title'] = 'as data source'
+    resp.forms['save-custom-view']['visibility'] = 'datasource'
+    resp = resp.forms['save-custom-view'].submit().follow()
+
+    assert resp.forms['listing-settings']['filter-0-value'].attrs['data-allow-template']
+    assert 'custom value' in [x[2] for x in resp.forms['listing-settings']['filter-0-value'].options]
+    resp.forms['listing-settings']['filter-0-value'].force_value('{{ form_var_blah }}')
+
+    resp = resp.forms['listing-settings'].submit()
+    assert resp.forms['listing-settings']['filter-0-value'].value == '{{ form_var_blah }}'
+    assert resp.text.count('<tr') == 1  # thead only
+
+    # save custom view with filter
+    resp = resp.forms['save-custom-view'].submit().follow()
+
+    custom_view = pub.custom_view_class.select()[0]
+
+    # use custom view as source
+    ds = {'type': 'carddef:%s:%s' % (carddef.url_name, custom_view.slug)}
+    formdef.fields = [
+        fields.ItemField(id='0', type='item', label='item', varname='blah', data_source=data_source),
+        fields.ItemField(id='1', label='string', type='item', data_source=ds, display_disabled_items=True),
+    ]
+    formdef.store()
+
+    resp = get_app(pub).get('/test/')
+    assert resp.form['f1'].options == [('', False, '---')]
+    resp.form['f0'] = 'baz'  # I guess it should be something like f0_display
+    live_resp = app.post('/test/live?modified_field_id=0', params=resp.form.submit_fields())
+    assert len(live_resp.json['result']['1']['items']) == 10
+    assert {x['id'] for x in live_resp.json['result']['1']['items']} == baz_ids
+
+    resp.form['f1'].options = []
+    for item in live_resp.json['result']['1']['items']:
+        # simulate javascript filling the <select>
+        resp.form['f1'].options.append((item['id'], False, item['text']))
+
+    resp.form['f1'] = resp.form['f1'].options[0][0]
+    resp = resp.form.submit('submit')  # -> validation page
+    resp = resp.form.submit('submit')  # -> submit
+    assert formdef.data_class().select()[0].data['1'] in baz_ids
+
+    # same in autocomplete mode
+    formdef.fields[1].display_mode = 'autocomplete'
+    formdef.store()
+    app = get_app(pub)
+    resp = app.get('/test/')
+    # simulate select2 mode, with qommon.forms.js adding an extra hidden widget
+    resp.form.fields['f1_display'] = Hidden(form=resp.form, tag='input', name='f1_display', pos=10)
+    select2_url = resp.pyquery('select:last').attr['data-select2-url']
+    resp_json = app.get(select2_url + '?q=')
+    assert len(resp_json.json['data']) == 0
+    resp.form['f0'] = 'baz'  # I guess it should be something like f0_display
+
+    live_resp = app.post('/test/live?modified_field_id=0', params=resp.form.submit_fields())
+    new_select2_url = live_resp.json['result']['1']['source_url']
+    resp_json = app.get(new_select2_url + '?q=')
+    assert len(resp_json.json['data']) == 10
+    assert {str(x['id']) for x in resp_json.json['data']} == baz_ids
+
+    resp.form['f1'].force_value(str(resp_json.json['data'][0]['id']))
+    resp.form.fields['f1_display'].force_value(resp_json.json['data'][0]['text'])
+
+    resp = resp.form.submit('submit')  # -> validation page
+    resp = resp.form.submit('submit')  # -> submit
+    assert formdef.data_class().select()[0].data['1'] in baz_ids
+
+
 def test_item_field_from_cards_check_lazy_live(pub):
     create_user(pub)
     formdef = create_formdef()
     formdef.data_class().wipe()
 
     CardDef.wipe()
     carddef = CardDef()
     carddef.name = 'items'
-- 
2.30.2

