"""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