107 lines
4.7 KiB
Python
107 lines
4.7 KiB
Python
|
|
"""V8.24: persisted 2-level layout editing, safe public rendering and migration compatibility."""
|
||
|
|
from pathlib import Path
|
||
|
|
import pytest
|
||
|
|
from bs4 import BeautifulSoup
|
||
|
|
from app import blueprints
|
||
|
|
from tests.test_factory import client,create
|
||
|
|
|
||
|
|
ROOT=Path(__file__).resolve().parents[1]
|
||
|
|
|
||
|
|
|
||
|
|
def make_nested():
|
||
|
|
root=blueprints.new_block('layout')
|
||
|
|
inner=blueprints.new_block('layout',{'gap':18})
|
||
|
|
leaf=blueprints.new_block('image_text',{'title':'Inner leaf title','description':'Nested content'})
|
||
|
|
inner['columns'][0]['blocks']=[leaf]
|
||
|
|
root['columns'][1]['blocks']=[inner]
|
||
|
|
return root,inner,leaf
|
||
|
|
|
||
|
|
|
||
|
|
def test_deep_nested_output_valid_and_safe():
|
||
|
|
root,inner,leaf=make_nested()
|
||
|
|
clean=blueprints.validate_blocks([root])
|
||
|
|
markup=blueprints.render_blocks(clean,{'name':'Example'},{'title':'Home'},[])
|
||
|
|
soup=BeautifulSoup(markup,'html.parser')
|
||
|
|
assert soup.select_one(f'.sf-layout-grid[data-layout="{root["id"]}"]')
|
||
|
|
assert soup.select_one(f'.sf-layout-grid[data-layout="{inner["id"]}"]')
|
||
|
|
assert soup.select_one(f'.sf-layout-column .sf-section[data-block="{leaf["id"]}"]')
|
||
|
|
assert 'Inner leaf title' in soup.get_text(' ')
|
||
|
|
assert 'siteforge-studio-canvas' not in markup # never emitted into public HTML
|
||
|
|
|
||
|
|
|
||
|
|
def test_reject_exceeding_allowed_nested_layout_depth():
|
||
|
|
root,inner,_=make_nested()
|
||
|
|
extra=blueprints.new_block('layout')
|
||
|
|
inner['columns'][0]['blocks'].append(extra)
|
||
|
|
with pytest.raises(ValueError,match='最多嵌套两层'):
|
||
|
|
blueprints.validate_blocks([root])
|
||
|
|
|
||
|
|
|
||
|
|
def test_reject_duplicate_ids_in_deep_nested_layout():
|
||
|
|
root,inner,leaf=make_nested()
|
||
|
|
inner['columns'][1]['blocks'].append({**leaf})
|
||
|
|
with pytest.raises(ValueError,match='重复'):
|
||
|
|
blueprints.validate_blocks([root])
|
||
|
|
|
||
|
|
|
||
|
|
def test_deep_nested_draft_and_history_round_trip(client):
|
||
|
|
sid=create(client)
|
||
|
|
eid=next(row['id'] for row in client.get(f'/api/sites/{sid}/entries').json() if row['route']=='/')
|
||
|
|
root,inner,leaf=make_nested()
|
||
|
|
url=f'/api/sites/{sid}/pages/{eid}/studio-draft'
|
||
|
|
saved=client.put(url,json={'blocks':[root],'page_template':'home','layout_mode':'replace','expected_revision':0})
|
||
|
|
assert saved.status_code==200,saved.text
|
||
|
|
loaded=client.get(url);assert loaded.status_code==200
|
||
|
|
assert loaded.json()['blocks'][0]['columns'][1]['blocks'][0]['columns'][0]['blocks'][0]['id']==leaf['id']
|
||
|
|
assert client.put(url,json={'blocks':[root],'page_template':'home','layout_mode':'replace','expected_revision':0}).status_code==409
|
||
|
|
|
||
|
|
|
||
|
|
def test_deep_nested_editor_helpers_and_isolated_script():
|
||
|
|
helper=(ROOT/'app/static/editor-helpers.js').read_text()
|
||
|
|
editor=(ROOT/'app/static/editor.js').read_text()
|
||
|
|
bridge=(ROOT/'app/static/studio-canvas-bridge.js').read_text()
|
||
|
|
assert 'findChildInRoot' in helper
|
||
|
|
assert 'findLayout' in helper
|
||
|
|
assert 'setNestedLayoutPreset' in editor
|
||
|
|
assert 'setNestedLayoutGap' in editor
|
||
|
|
assert 'childQuickEditor' in editor
|
||
|
|
assert 'siteforge-studio-canvas' in bridge
|
||
|
|
assert 'event.source!==frame.contentWindow' in editor
|
||
|
|
assert 'eval(' not in bridge and 'new Function(' not in bridge
|
||
|
|
|
||
|
|
|
||
|
|
def test_nested_preview_endpoint_and_responsive_values(client):
|
||
|
|
sid=create(client)
|
||
|
|
eid=next(row['id'] for row in client.get(f'/api/sites/{sid}/entries').json() if row['route']=='/')
|
||
|
|
root,inner,leaf=make_nested()
|
||
|
|
inner['mobile_columns']=1
|
||
|
|
inner['tablet_columns']=2
|
||
|
|
inner['gap']=32
|
||
|
|
preview=client.post(f'/api/sites/{sid}/pages/{eid}/editor-preview',json={
|
||
|
|
'blocks':[root],'page_template':'home','layout_mode':'replace'})
|
||
|
|
assert preview.status_code==200,preview.text[:600]
|
||
|
|
assert 'Inner leaf title' in preview.text
|
||
|
|
assert 'data-layout="'+inner['id']+'"' in preview.text
|
||
|
|
assert 'studio-canvas-bridge.js' in preview.text or 'siteforge-studio-canvas' in preview.text
|
||
|
|
|
||
|
|
|
||
|
|
def test_editor_uses_no_json_input_for_deep_nested_components():
|
||
|
|
source=(ROOT/'app/static/editor.js').read_text()
|
||
|
|
for feature in ('addNestedChild','setNestedDeviceColumns','setNestedLayoutGap','setNestedLayoutPreset',
|
||
|
|
'findChildInRoot','findLayout','Studio.applyMedia(r.url)'):
|
||
|
|
assert feature in source
|
||
|
|
|
||
|
|
|
||
|
|
def test_save_nested_leaf_as_site_scoped_reusable_component(client):
|
||
|
|
sid=create(client)
|
||
|
|
_,_,leaf=make_nested()
|
||
|
|
created=client.post(f'/api/sites/{sid}/components',json={'name':'海运图文', 'block':leaf})
|
||
|
|
assert created.status_code==200,created.text
|
||
|
|
cid=created.json()['id']
|
||
|
|
loaded=client.get(f'/api/sites/{sid}/components')
|
||
|
|
assert loaded.status_code==200
|
||
|
|
assert any(row['id']==cid and row['block']['title']=='Inner leaf title' for row in loaded.json())
|
||
|
|
stale=client.put(f'/api/sites/{sid}/components/{cid}',json={
|
||
|
|
'name':'不允许覆盖','expected_version':0,'block':leaf})
|
||
|
|
assert stale.status_code==409
|