upgrade: adopt V8.12.0-dev.1 (V8.11 unified professional UI + V8.12 workflow usability)
- Overlay 26 changed files from SiteForge_Enterprise_V8_12 (UI layer only: professional-ui.js/css, workspace/factory-v2/operations/editor templates, +2 test files, compose/ci/scripts/docs) - Re-apply v8_workflows type-mapping fix (类型/分类) lost in upgrade - Pin compose project name to siteforge-v8-10 so existing data volumes (hesupipe site 483295c29ea0, 193 entries) keep attached - Drop stray .s10_* state files; ignore .s10_*/.sf_*/.s87_* patterns - Plan/spec revisions per plan-review round 1 (icons are new design, real test assertions, recorded deviations) Tests: 226 passed / 1 env-only failure (test needs node binary in PATH, absent in python:3.12-slim container; host node v24 --check all 9 JS OK) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
"""V8.11: specialized tools share a single task-oriented UI without changing APIs."""
|
||||
from test_factory import client
|
||||
|
||||
PROFESSIONAL = {
|
||||
'/editor/{site}/{entry}': 'editor',
|
||||
'/theme-gallery': 'theme',
|
||||
'/factory-studio': 'factory',
|
||||
'/operations': 'operations',
|
||||
'/production-v8': 'production',
|
||||
}
|
||||
|
||||
|
||||
def create_site(client):
|
||||
response = client.post('/api/sites', json={'name': 'V8.11 UI', 'theme': 'corporate'})
|
||||
assert response.status_code == 201, response.text
|
||||
sid = response.json()['id']
|
||||
entry = client.get(f'/api/sites/{sid}/entries').json()[0]
|
||||
return sid, entry['id']
|
||||
|
||||
|
||||
def test_all_professional_pages_load_unified_shell(client):
|
||||
site, entry = create_site(client)
|
||||
for pattern, page_type in PROFESSIONAL.items():
|
||||
uri = pattern.format(site=site, entry=entry)
|
||||
response = client.get(uri)
|
||||
assert response.status_code == 200, uri
|
||||
assert f'data-sf-page="{page_type}"' in response.text, uri
|
||||
assert '/ui/professional-ui.js' in response.text
|
||||
assert '/ui/professional-ui.css' in response.text
|
||||
|
||||
|
||||
def test_shared_navigation_assets_are_real_routes(client):
|
||||
css = client.get('/ui/professional-ui.css')
|
||||
js = client.get('/ui/professional-ui.js')
|
||||
assert css.status_code == 200 and js.status_code == 200
|
||||
assert 'sf-bar-nav' in css.text and '.sf-editor-mobile-tabs' in css.text
|
||||
for code in ('siteforge:selected-site', 'sfGlobalSite', 'aria-current', 'MutationObserver'):
|
||||
assert code in js.text
|
||||
|
||||
|
||||
def test_site_switcher_avoids_cross_site_editor_paths(client):
|
||||
js = client.get('/ui/professional-ui.js').text
|
||||
assert "ctx==='editor'" in js
|
||||
assert "location.assign(route('design'))" in js
|
||||
assert 'items.some(s=>s.id===wanted)' in js # selected ID validated against visible sites
|
||||
assert 'siteforge:selected-site' in client.get('/ui/workspace.js').text
|
||||
|
||||
|
||||
def test_professional_forms_keep_validation_and_csrf(client):
|
||||
js = client.get('/ui/professional-ui.js').text
|
||||
assert "addEventListener('invalid'" in js
|
||||
assert "aria-invalid" in js
|
||||
assert 'X-SiteForge-CSRF' in client.get('/ui/operations.js').text
|
||||
assert 'X-SiteForge-CSRF' in client.get('/ui/workspace.js').text
|
||||
|
||||
|
||||
def test_operations_are_split_into_three_steps(client):
|
||||
shell = client.get('/ui/professional-ui.js').text
|
||||
assert '② 生成静态网站' in shell
|
||||
assert '③ 审批与发布' in shell
|
||||
assert "role','tabpanel'" in shell
|
||||
page = client.get('/operations').text
|
||||
assert all(word in page for word in ('CMS 数据迁移', '生产构建任务', '远程发布'))
|
||||
|
||||
|
||||
def test_editor_preserves_draft_publish_and_mobile_panels(client):
|
||||
site, entry = create_site(client)
|
||||
html = client.get(f'/editor/{site}/{entry}').text
|
||||
assert 'Studio.save()' in html and 'Studio.publishDesign()' in html
|
||||
js = client.get('/ui/professional-ui.js').text
|
||||
assert all(word in js for word in ('结构', '页面预览', '属性设置', "dataset.mobilePane"))
|
||||
assert 'sf-editor-actiongroup' in js
|
||||
|
||||
|
||||
def test_health_and_main_workspace_match_unified_release(client):
|
||||
assert client.get('/api/health').json()['version'] == '8.12.0-dev.1'
|
||||
assert 'const headings=' in client.get('/ui/workspace.js').text
|
||||
assert 'new URLSearchParams(location.search)' in client.get('/ui/workspace.js').text
|
||||
|
||||
|
||||
def test_existing_theme_and_content_apis_unchanged(client):
|
||||
sid, _ = create_site(client)
|
||||
assert client.get('/api/themes').status_code == 200
|
||||
assert client.get(f'/api/sites/{sid}/entries').status_code == 200
|
||||
assert client.get(f'/api/sites/{sid}/builds').status_code == 200
|
||||
@@ -145,7 +145,7 @@ def test_v8_api_authenticated_and_browser_page(env):
|
||||
assert client.get(f'/api/factory/sites/{sid}/staging').json()['items'][0]['state']=='committed'
|
||||
snap=client.post(f'/api/factory/sites/{sid}/whole-site-templates',json={'name':'Test'})
|
||||
assert snap.status_code==200,snap.text
|
||||
assert client.get('/api/health').json()['version']=='8.10.0-dev.1'
|
||||
assert client.get('/api/health').json()['version']=='8.12.0-dev.1'
|
||||
|
||||
|
||||
def test_wxr_preserves_post_type(env):
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
"""V8.12 workbench usability, security boundaries and backend compatibility."""
|
||||
from test_factory import client
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_workbench_has_real_entry_and_assets(client):
|
||||
html=client.get('/').text
|
||||
assert '/ui/workspace.js' in html and 'sitePicker' in html
|
||||
assert client.get('/ui/workspace.js').status_code==200
|
||||
assert client.get('/ui/workspace.css').status_code==200
|
||||
|
||||
|
||||
def test_authorized_site_restoration_and_browser_history(client):
|
||||
js=client.get('/ui/workspace.js').text
|
||||
assert 'siteforge:selected-site' in js
|
||||
assert "state.sites.some(x=>x.id===id)" in js
|
||||
assert "state.sites.some(x=>x.id===id)" in js
|
||||
assert "window.addEventListener('popstate'" in js
|
||||
assert "history[replace?'replaceState':'pushState']" in js
|
||||
assert "const token=++state.selectionToken" in js
|
||||
|
||||
|
||||
def test_build_screen_recovers_durable_worker_jobs(client):
|
||||
js=client.get('/ui/workspace.js').text
|
||||
assert '/api/enterprise/sites/' in js
|
||||
assert 'function activeJobs()' in js
|
||||
assert 'refreshBuilds()' in js
|
||||
assert 'cancel-job' in js and 'async function cancelJob' in js
|
||||
assert '交付前检查' in js and '/migration/readiness' in js
|
||||
assert 'setInterval' in js and '!document.hidden' in js
|
||||
|
||||
|
||||
def test_build_rejects_download_while_incomplete(client):
|
||||
js=client.get('/ui/workspace.js').text
|
||||
assert '尚未完成,暂不能下载' in js
|
||||
assert 'status' in js and "['done','completed','success','complete']" in js
|
||||
|
||||
|
||||
def test_safe_import_is_default_and_direct_import_is_explicit(client):
|
||||
js=client.get('/ui/workspace.js').text
|
||||
assert '推荐:先预演再导入' in js
|
||||
assert '直接写入网站(不可批次回滚)' in js
|
||||
assert '安全导入与批次回滚' in js
|
||||
assert "if(!confirm('将文件内容直接写入" in js
|
||||
|
||||
|
||||
def test_wizard_does_not_claim_design_automatically_finished(client):
|
||||
js=client.get('/ui/workspace.js').text
|
||||
assert 'optional:true,view:\'design\'' in js
|
||||
assert '不自动标记为完成' not in js or '设计可以随时修改' in js
|
||||
|
||||
|
||||
def test_live_job_endpoint_can_list_user_site_jobs(client):
|
||||
site=client.post('/api/sites',json={'name':'UI V8.12 smoke','theme':'corporate'})
|
||||
assert site.status_code==201,site.text
|
||||
sid=site.json()['id']
|
||||
result=client.get(f'/api/enterprise/sites/{sid}/jobs')
|
||||
assert result.status_code==200,result.text
|
||||
assert isinstance(result.json(),list)
|
||||
audit=client.get(f'/api/factory/sites/{sid}/migration/readiness')
|
||||
assert audit.status_code==200,audit.text
|
||||
assert 'gates' in audit.json()
|
||||
|
||||
|
||||
def test_version_is_updated(client):
|
||||
assert client.get('/api/health').json()['version']=='8.12.0-dev.1'
|
||||
|
||||
|
||||
def test_js_standalone_syntax():
|
||||
root=Path(__file__).resolve().parents[1]
|
||||
proc=subprocess.run(['node','--check',str(root/'app/static/workspace.js')],capture_output=True,text=True)
|
||||
assert proc.returncode==0,proc.stderr
|
||||
Reference in New Issue
Block a user