"""V8.21.3 mobile navigation must be optional, safe and theme-independent.""" import copy import pytest from tests.test_factory import client, create from app import site_design_v87, builder, db def config(tabs=True): x=copy.deepcopy(site_design_v87.DEFAULTS) x['mobile_tabs']={'enabled':tabs,'items':[ {'title':'首页','icon':'home','route':'/','action':'link'}, {'title':'产品','icon':'products','route':'/products/','action':'link'}, {'title':'菜单','icon':'menu','route':'/','action':'menu'}, ]} return x @pytest.mark.parametrize('theme',['automotive','industrial','corporate','minimal']) def test_all_four_professional_themes_render_optional_bar(client,theme): sid=create(client,theme) url=f'/api/sites/{sid}/site-design' before=builder.build(sid,False) previous=(db.DATA/'builds'/sid/before['version']/'dist/index.html').read_text() assert 'data-mobile-tabs' not in previous payload=config() stored=client.put(url,json={'config':payload,'expected_revision':0}) assert stored.status_code==200,stored.text waiting=builder.build(sid,False) assert 'data-mobile-tabs' not in (db.DATA/'builds'/sid/waiting['version']/'dist/index.html').read_text() published=client.post(url+'/publish',json={'expected_revision':1}) assert published.status_code==200,published.text after=builder.build(sid,False) dist=db.DATA/'builds'/sid/after['version']/'dist' html=(dist/'index.html').read_text() css=(dist/'assets/commercial.css').read_text() js=(dist/'assets/pro.js').read_text() assert 'data-mobile-tabs' in html and 'data-mobile-open-menu' in html assert 'aria-current="page"' in html assert '手机底部快捷导航' in html assert 'sf-drawer-header' in css and 'sf-drawer-open' in css assert 'sf-mobile-drawer-backdrop' in js and 'Escape' in js assert len(html)>2000 def test_tabs_can_be_disabled_while_items_remain(client): sid=create(client) url=f'/api/sites/{sid}/site-design' payload=config(False) assert client.put(url,json={'config':payload,'expected_revision':0}).status_code==200 assert client.post(url+'/publish',json={'expected_revision':1}).status_code==200 dist=db.DATA/'builds'/sid/builder.build(sid,False)['version']/'dist' assert 'data-mobile-tabs' not in (dist/'index.html').read_text() @pytest.mark.parametrize('bad',[{'enabled':True,'items':[]}, {'enabled':'yes','items':[]}, {'enabled':True,'items':[{'title':'Hello','icon':'script','route':'/'}]}, {'enabled':True,'items':[{'title':'测试','icon':'home','route':'javascript:alert(1)'}]}, {'enabled':True,'items':[{'title':'测试','icon':'home','route':'//127.0.0.1'}]}, {'enabled':True,'items':[{'title':'X'*20,'icon':'home','route':'/'}]}, {'enabled':True,'items':[{'title':'1','icon':'home','route':'/'}]*6}, {'enabled':True,'items':[{'title':'ok','icon':'home','action':'eval','route':'/'}]}]) def test_invalid_mobile_tabs_rejected(bad): payload=config() payload['mobile_tabs']=bad with pytest.raises(Exception):site_design_v87.validate(payload) def test_tab_config_is_normalized_and_legacy_defaults_are_safe(): legacy={'header':{},'responsive':{},'tokens':{},'footer':{},'nav':[]} assert site_design_v87.validate(legacy)['mobile_tabs']=={'enabled':False,'items':[]} planned=config() out=site_design_v87.validate(planned) assert out['mobile_tabs']['items'][-1]['action']=='menu' assert out['mobile_tabs']['items'][-1]['route']=='/' def test_contact_links_are_configurable_but_unsafe_schemes_are_blocked(): for url in ('https://wa.me/12345678','tel:+8613800000000','mailto:hello@example.com'): v=config();v['mobile_tabs']['items'][1]['route']=url assert site_design_v87.validate(v)['mobile_tabs']['items'][1]['route']==url for url in ('http://example.com','javascript:alert(1)','data:text/html,hi','https://user:pass@example.com/','https://bad.example/\nnewline'): v=config();v['mobile_tabs']['items'][1]['route']=url with pytest.raises(Exception):site_design_v87.validate(v)